105 lines
4 KiB
TypeScript
105 lines
4 KiB
TypeScript
import { faCrosshairs } from '@fortawesome/free-solid-svg-icons';
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
import { CSSProperties, useEffect, useReducer, useState } from 'react';
|
|
import BorderTiles from './components/BorderTiles';
|
|
import FogImages from './components/FogImages';
|
|
import HitElems from './components/HitElems';
|
|
import Labeling from './components/Labeling';
|
|
import Ships from './components/Ships';
|
|
import { hitReducer, initlialTarget, initlialTargetPreview, isHit } from './helpers';
|
|
import { HitType, TargetPreviewType, TargetType } from './interfaces';
|
|
import './styles/App.scss';
|
|
|
|
function App() {
|
|
|
|
const count = 12;
|
|
const [target, setTarget] = useState<TargetType>(initlialTarget);
|
|
const [targetPreview, setTargetPreview] = useState<TargetPreviewType>(initlialTargetPreview);
|
|
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);
|
|
// if not ready or no new position
|
|
if (!eventReady || !positionChange)
|
|
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) {
|
|
// 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])
|
|
|
|
// enable targetPreview event again after 200 mil. sec.
|
|
useEffect(() => {
|
|
if (targetPreview.eventReady)
|
|
return;
|
|
const autoTimeout = setTimeout(() => {
|
|
setTargetPreview(e => ({...e, eventReady: true}));
|
|
}, 200);
|
|
|
|
// or abort if state has changed early
|
|
return () => {
|
|
clearTimeout(autoTimeout);
|
|
}
|
|
}, [targetPreview.eventReady]);
|
|
|
|
// 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);
|
|
|
|
// or abort if movement is repeated early
|
|
return () => {
|
|
clearTimeout(autoTimeout);
|
|
}
|
|
}, [targetPreview.shouldShow, targetPreview.newX, targetPreview.newY]);
|
|
|
|
return (
|
|
<div className="App">
|
|
<header className="App-header">
|
|
<div id="game-frame" style={{'--i': count} as CSSProperties}>
|
|
{/* Bordes */}
|
|
<BorderTiles count={count} actions={{setTarget, setTargetPreview, hits, DispatchHits}} />
|
|
|
|
{/* Collumn lettes and row numbers */}
|
|
<Labeling count={count} />
|
|
|
|
{/* Ships */}
|
|
{/* <Ships /> */}
|
|
|
|
<HitElems hits={hits} />
|
|
|
|
{/* Fog images */}
|
|
{/* <FogImages /> */}
|
|
<div className={`hit-svg target ${target.show ? 'show' : ''}`} style={{'--x': target.x, '--y': target.y} as CSSProperties}>
|
|
<FontAwesomeIcon icon={faCrosshairs} />
|
|
</div>
|
|
<div className={`hit-svg target-preview ${targetPreview.show && (target.x !== targetPreview.x || target.y !== targetPreview.y) ? 'show' : ''}`} style={{'--x': targetPreview.x, '--y': targetPreview.y} as CSSProperties}>
|
|
<FontAwesomeIcon icon={faCrosshairs} />
|
|
</div>
|
|
</div>
|
|
{/* <p>
|
|
Edit <code>src/App.tsx</code> and save to reload.
|
|
</p> */}
|
|
<a
|
|
className="App-link"
|
|
href="https://www.freepik.com/free-vector/militaristic-ships-set-navy-ammunition-warship-submarine-nuclear-battleship-float-cruiser-trawler-gunboat-frigate-ferry_10704121.htm"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
Battleships designed by macrovector
|
|
</a>
|
|
</header>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default App;
|