Rewrote targetPreview for pulsation effect
This commit is contained in:
parent
7196f45ee8
commit
9230b06f6c
5 changed files with 67 additions and 55 deletions
|
@ -1,8 +1,17 @@
|
||||||
import { CSSProperties, Dispatch, SetStateAction } from 'react';
|
import { CSSProperties, Dispatch, SetStateAction } from 'react';
|
||||||
import { borderCN, cornerCN, fieldIndex, isHit } from '../helpers';
|
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: {
|
let tilesProperties: {
|
||||||
key: number,
|
key: number,
|
||||||
isGameTile: boolean,
|
isGameTile: boolean,
|
||||||
|
@ -33,9 +42,9 @@ function BorderTiles({ count, actions: { setTarget, setTargetPreview, hits, Disp
|
||||||
className={classNameString}
|
className={classNameString}
|
||||||
style={{ '--x': x, '--y': y } as CSSProperties}
|
style={{ '--x': x, '--y': y } as CSSProperties}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!isGameTile && !isHit(hits, x, y).length)
|
if (!isGameTile || isHit(hits, x, y).length)
|
||||||
return;
|
return;
|
||||||
setTargetPreview(e => ({ ...e, shouldShow: false, show: false }))
|
setTargetPreviewPos(e => ({ ...e, shouldShow: false }))
|
||||||
setTarget(t => {
|
setTarget(t => {
|
||||||
if (t.x === x && t.y === y) {
|
if (t.x === x && t.y === y) {
|
||||||
DispatchHits({ type: 'fireMissle', payload: { hit: (x + y) % 2 !== 0, x, 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>
|
></div>
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -7,8 +7,8 @@ import BorderTiles from './BorderTiles';
|
||||||
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, initlialTarget, initlialTargetPreview, isHit } from '../helpers';
|
import { hitReducer, initlialLastLeftTile, initlialTarget, initlialTargetPreview, initlialTargetPreviewPos, isHit } from '../helpers';
|
||||||
import { HitType, TargetPreviewType, TargetType } from '../interfaces';
|
import { HitType, LastLeftTileType, TargetPreviewPosType, TargetPreviewType, TargetType } from '../interfaces';
|
||||||
import Item from './Item';
|
import Item from './Item';
|
||||||
|
|
||||||
function Gamefield() {
|
function Gamefield() {
|
||||||
|
@ -22,62 +22,65 @@ function Gamefield() {
|
||||||
]
|
]
|
||||||
|
|
||||||
const count = 12;
|
const count = 12;
|
||||||
|
const [lastLeftTile, setLastLeftTile] = useState<LastLeftTileType>(initlialLastLeftTile);
|
||||||
const [target, setTarget] = useState<TargetType>(initlialTarget);
|
const [target, setTarget] = useState<TargetType>(initlialTarget);
|
||||||
|
const [eventReady, setEventReady] = useState(false);
|
||||||
|
const [appearOK, setAppearOK] = useState(false);
|
||||||
const [targetPreview, setTargetPreview] = useState<TargetPreviewType>(initlialTargetPreview);
|
const [targetPreview, setTargetPreview] = useState<TargetPreviewType>(initlialTargetPreview);
|
||||||
|
const [targetPreviewPos, setTargetPreviewPos] = useState<TargetPreviewPosType>(initlialTargetPreviewPos);
|
||||||
const [hits, DispatchHits] = useReducer(hitReducer, [] as HitType[]);
|
const [hits, DispatchHits] = useReducer(hitReducer, [] as HitType[]);
|
||||||
|
|
||||||
// handle visibility and position change of targetPreview
|
// handle visibility and position change of targetPreview
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { newX, newY, shouldShow, appearOK, eventReady, show, x, y } = targetPreview;
|
const { show, x, y } = targetPreview;
|
||||||
const positionChange = !(x === newX && y === newY);
|
const { shouldShow } = targetPreviewPos;
|
||||||
const alreadyTargeting = target.show && target.x === targetPreview.newX && target.y === targetPreview.newY
|
// if mouse has moved too quickly and last event was entering and leaving the same field, it must have gone outside the grid
|
||||||
// if not ready or no new position
|
const hasLeft = x === lastLeftTile.x && y === lastLeftTile.y
|
||||||
if (!eventReady || (!positionChange && show))
|
const isSet = x === target.x && y === target.y
|
||||||
return;
|
|
||||||
if (show) {
|
if (show && !appearOK)
|
||||||
// hide preview to change position when hidden
|
setTargetPreview(e => ({ ...e, show: false }));
|
||||||
setTargetPreview(e => ({ ...e, appearOK: false, eventReady: false, show: false }));
|
if (!show && shouldShow && eventReady && appearOK && !isHit(hits, x, y).length && !hasLeft && !isSet)
|
||||||
} else if (shouldShow && appearOK && !isHit(hits, newX, newY).length && !alreadyTargeting) {
|
setTargetPreview(e => ({ ...e, show: true }));
|
||||||
// BUT only appear again if it's supposed to (in case the mouse left over the edge) and ()
|
}, [targetPreview, hits, eventReady, appearOK, lastLeftTile])
|
||||||
setTargetPreview(e => ({ ...e, appearOK: false, eventReady: false, show: true, x: newX, y: newY }));
|
|
||||||
}
|
|
||||||
}, [targetPreview, hits])
|
|
||||||
|
|
||||||
// enable targetPreview event again after 200 mil. sec.
|
// enable targetPreview event again after 200 mil. sec.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (targetPreview.eventReady)
|
const { x: newX, y: newY } = targetPreviewPos;
|
||||||
|
setEventReady(false);
|
||||||
|
if (targetPreview.show || !appearOK)
|
||||||
return;
|
return;
|
||||||
const autoTimeout = setTimeout(() => {
|
const autoTimeout = setTimeout(() => {
|
||||||
setTargetPreview(e => ({ ...e, eventReady: true }));
|
setTargetPreview(e => ({ ...e, x: newX, y: newY }));
|
||||||
}, 200);
|
setEventReady(true);
|
||||||
|
setAppearOK(true);
|
||||||
|
}, 250);
|
||||||
|
|
||||||
// or abort if state has changed early
|
// or abort if state has changed early
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(autoTimeout);
|
clearTimeout(autoTimeout);
|
||||||
}
|
}
|
||||||
}, [targetPreview.eventReady]);
|
}, [targetPreviewPos, targetPreview.show, appearOK]);
|
||||||
|
|
||||||
// approve targetPreview new position after 200 mil. sec.
|
// approve targetPreview new position after 200 mil. sec.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// early return to start cooldown only when about to show up
|
// early return to start cooldown only when about to show up
|
||||||
if (!targetPreview.shouldShow)
|
|
||||||
return;
|
|
||||||
const autoTimeout = setTimeout(() => {
|
const autoTimeout = setTimeout(() => {
|
||||||
setTargetPreview(e => ({ ...e, appearOK: true }));
|
setAppearOK(!targetPreview.show)
|
||||||
}, 350);
|
}, targetPreview.show ? 500 : 100);
|
||||||
|
|
||||||
// or abort if movement is repeated early
|
// or abort if movement is repeated early
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(autoTimeout);
|
clearTimeout(autoTimeout);
|
||||||
}
|
}
|
||||||
}, [targetPreview.shouldShow, targetPreview.newX, targetPreview.newY]);
|
}, [targetPreview.show]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id='gamefield'>
|
<div id='gamefield'>
|
||||||
{/* <Bluetooth /> */}
|
{/* <Bluetooth /> */}
|
||||||
<div id="game-frame" style={{ '--i': count } as CSSProperties}>
|
<div id="game-frame" style={{ '--i': count } as CSSProperties}>
|
||||||
{/* Bordes */}
|
{/* Bordes */}
|
||||||
<BorderTiles count={count} actions={{ setTarget, setTargetPreview, hits, DispatchHits }} />
|
<BorderTiles props={{ count, setTarget, setTargetPreviewPos, hits, DispatchHits, setLastLeftTile }} />
|
||||||
|
|
||||||
{/* Collumn lettes and row numbers */}
|
{/* Collumn lettes and row numbers */}
|
||||||
<Labeling count={count} />
|
<Labeling count={count} />
|
||||||
|
@ -89,16 +92,16 @@ function Gamefield() {
|
||||||
|
|
||||||
{/* Fog images */}
|
{/* Fog images */}
|
||||||
{/* <FogImages /> */}
|
{/* <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} />
|
<FontAwesomeIcon icon={faCrosshairs} />
|
||||||
</div>
|
</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} />
|
<FontAwesomeIcon icon={faCrosshairs} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='event-bar'>
|
<div className='event-bar'>
|
||||||
{items.map(e => (
|
{items.map((e, i) => (
|
||||||
<Item props={e} />
|
<Item key={i} props={e} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -35,19 +35,23 @@ export const hitReducer = (formObject: HitType[], action: HitDispatchType) => {
|
||||||
return formObject;
|
return formObject;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export const initlialLastLeftTile = {
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
export const initlialTarget = {
|
export const initlialTarget = {
|
||||||
show: false,
|
show: false,
|
||||||
x: 2,
|
x: 2,
|
||||||
y: 2
|
y: 2
|
||||||
};
|
};
|
||||||
export const initlialTargetPreview = {
|
export const initlialTargetPreview = {
|
||||||
newX: 0,
|
|
||||||
newY: 0,
|
|
||||||
shouldShow: false,
|
|
||||||
appearOK: false,
|
|
||||||
eventReady: true,
|
|
||||||
show: false,
|
show: false,
|
||||||
x: 2,
|
x: 2,
|
||||||
y: 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);
|
export const isHit = (hits: HitType[], x: number, y: number) => hits.filter(h => h.x === x && h.y === y);
|
||||||
|
|
|
@ -1,18 +1,22 @@
|
||||||
|
export interface LastLeftTileType {
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
}
|
||||||
export interface TargetType {
|
export interface TargetType {
|
||||||
show: boolean,
|
show: boolean,
|
||||||
x: number,
|
x: number,
|
||||||
y: number
|
y: number
|
||||||
};
|
};
|
||||||
export interface TargetPreviewType {
|
export interface TargetPreviewType {
|
||||||
newX: number,
|
|
||||||
newY: number,
|
|
||||||
shouldShow: boolean,
|
|
||||||
appearOK: boolean,
|
|
||||||
eventReady: boolean,
|
|
||||||
show: boolean,
|
show: boolean,
|
||||||
x: number,
|
x: number,
|
||||||
y: number
|
y: number
|
||||||
};
|
};
|
||||||
|
export interface TargetPreviewPosType {
|
||||||
|
shouldShow: boolean,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
}
|
||||||
export interface FieldType {
|
export interface FieldType {
|
||||||
field: string,
|
field: string,
|
||||||
x: number,
|
x: number,
|
||||||
|
|
|
@ -28,9 +28,6 @@ body {
|
||||||
.App-header {
|
.App-header {
|
||||||
background-color: $theme;
|
background-color: $theme;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
// @include flex-col;
|
|
||||||
// align-items: center;
|
|
||||||
// justify-content: center;
|
|
||||||
font-size: calc(10px + 2vmin);
|
font-size: calc(10px + 2vmin);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
@ -57,8 +54,6 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
#game-frame {
|
#game-frame {
|
||||||
// border: 1px solid orange;
|
|
||||||
// position: relative;
|
|
||||||
// $height: 864px;
|
// $height: 864px;
|
||||||
$height: 648px;
|
$height: 648px;
|
||||||
$width: $height;
|
$width: $height;
|
||||||
|
@ -71,7 +66,6 @@ body {
|
||||||
justify-items: center;
|
justify-items: center;
|
||||||
grid-template-rows: .75fr repeat(12, 1fr) .75fr;
|
grid-template-rows: .75fr repeat(12, 1fr) .75fr;
|
||||||
grid-template-columns: .75fr repeat(12, 1fr) .75fr;
|
grid-template-columns: .75fr repeat(12, 1fr) .75fr;
|
||||||
// grid-gap: 1px solid blue;
|
|
||||||
|
|
||||||
>.label {
|
>.label {
|
||||||
grid-column: var(--x);
|
grid-column: var(--x);
|
||||||
|
@ -95,7 +89,6 @@ body {
|
||||||
|
|
||||||
> :not(.border) {
|
> :not(.border) {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
// border: 1px solid red;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
>span {
|
>span {
|
||||||
|
@ -109,8 +102,6 @@ body {
|
||||||
position: relative;
|
position: relative;
|
||||||
@include flex-col;
|
@include flex-col;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
// justify-content: center;
|
|
||||||
border: 1px solid yellow;
|
|
||||||
grid-row: var(--x);
|
grid-row: var(--x);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
|
@ -172,7 +163,7 @@ body {
|
||||||
&.target-preview {
|
&.target-preview {
|
||||||
color: orange;
|
color: orange;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@include transition(.2s);
|
@include transition(.5s);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.show {
|
&.show {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue