Refactoring
This commit is contained in:
parent
83f187a7dc
commit
2ebaccfb2d
9 changed files with 231 additions and 118 deletions
|
@ -1,109 +1,41 @@
|
||||||
import { faBurst, faCrosshairs, faXmark } from '@fortawesome/free-solid-svg-icons';
|
import { faCrosshairs } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { CSSProperties, useEffect, useState } from 'react';
|
import { CSSProperties, useEffect, useReducer, useState } from 'react';
|
||||||
import { TargetPreviewType } from './interfaces';
|
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 } from './helpers';
|
||||||
|
import { HitType, TargetPreviewType, TargetType } from './interfaces';
|
||||||
import './styles/App.scss';
|
import './styles/App.scss';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
||||||
const [target, setTarget] = useState({
|
const count = 12;
|
||||||
show: false,
|
const [target, setTarget] = useState<TargetType>(initlialTarget);
|
||||||
x: 0,
|
const [targetPreview, setTargetPreview] = useState<TargetPreviewType>(initlialTargetPreview);
|
||||||
y: 0
|
const [hits, DispatchHits] = useReducer(hitReducer, [] as HitType[]);
|
||||||
})
|
const [x, setX] = useState(0);
|
||||||
const [targetPreview, setTargetPreview] = useState<TargetPreviewType>({
|
const [y, setY] = useState(0);
|
||||||
newX: 0,
|
|
||||||
newY: 0,
|
|
||||||
shouldShow: false,
|
|
||||||
appearOK: false,
|
|
||||||
eventReady: true,
|
|
||||||
show: false,
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
})
|
|
||||||
|
|
||||||
let borderTiles: JSX.Element[] = [];
|
useEffect(() => {
|
||||||
let shipElems: JSX.Element[] = [];
|
if (hits.length === count*count)
|
||||||
let elems2: {
|
return;
|
||||||
field: string,
|
if (x === count) {
|
||||||
x: number,
|
setX(0);
|
||||||
y: number,
|
setY(y => y+1);
|
||||||
}[] = [];
|
return;
|
||||||
let hitElems: {
|
|
||||||
field: string,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
}[] = [],
|
|
||||||
count = 12;
|
|
||||||
for (let y = 0; y < count; y++) {
|
|
||||||
elems2.push(...[
|
|
||||||
// Up
|
|
||||||
{ field: String.fromCharCode(65+y), x: y+2, y: 1 },
|
|
||||||
// Left
|
|
||||||
{ field: (y+1).toString(), x: 1, y: y+2 },
|
|
||||||
// Bottom
|
|
||||||
{ field: String.fromCharCode(65+y), x: y+2, y: count+2 },
|
|
||||||
// Right
|
|
||||||
{ field: (y+1).toString(), x: count+2, y: y+2 }
|
|
||||||
]);
|
|
||||||
for (let x = 0; x < count; x++) {
|
|
||||||
hitElems.push({ field: String.fromCharCode(65+x)+(y), x: x+2, y: y+2 })
|
|
||||||
}
|
}
|
||||||
}
|
const autoTimer = setTimeout(() => {
|
||||||
const hitSVGs = hitElems.map((obj, i) =>
|
DispatchHits({type: 'fireMissle', payload: {hit: (x+y)%2 !== 0, x: x+2, y: y+2}});
|
||||||
<div key={i} className='hit-svg' style={{'--x': obj.x, '--y': obj.y} as CSSProperties}>
|
setX(x => x+1);
|
||||||
<FontAwesomeIcon icon={faBurst} />
|
}, 100);
|
||||||
</div>);
|
|
||||||
const corner = (x: number, y: number, count: number) => {switch (true) {
|
return () => {
|
||||||
case x === 0 && y === 0:
|
clearTimeout(autoTimer);
|
||||||
return 'left-top-corner';
|
|
||||||
case x === count+1 && y === 0:
|
|
||||||
return 'right-top-corner';
|
|
||||||
case x === 0 && y === count+1:
|
|
||||||
return 'left-bottom-corner';
|
|
||||||
case x === count+1 && y === count+1:
|
|
||||||
return 'right-bottom-corner';
|
|
||||||
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}};
|
|
||||||
const border = (x: number, y: number, count: number) => {switch (true) {
|
|
||||||
case x === 0:
|
|
||||||
return 'left';
|
|
||||||
case y === 0:
|
|
||||||
return 'top';
|
|
||||||
case x === count+1:
|
|
||||||
return 'right';
|
|
||||||
case y === count+1:
|
|
||||||
return 'bottom';
|
|
||||||
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}};
|
|
||||||
for (let y = 0; y < count+2; y++) {
|
|
||||||
for (let x = 0; x < count+2; x++) {
|
|
||||||
const cornerReslt = corner(x, y, count);
|
|
||||||
const borderType = cornerReslt ? cornerReslt : border(x, y, count);
|
|
||||||
const isGameTile = x > 0 && x < count+1 && y > 0 && y < count+1;
|
|
||||||
const classNames = ['border-tile'];
|
|
||||||
if (borderType)
|
|
||||||
classNames.push('edge', borderType);
|
|
||||||
if (isGameTile)
|
|
||||||
classNames.push('game-tile');
|
|
||||||
borderTiles.push(
|
|
||||||
<div
|
|
||||||
key={y*(count+2)+x}
|
|
||||||
className={classNames.join(' ')}
|
|
||||||
style={{'--x': (x + 1), '--y': (y + 1)} as CSSProperties}
|
|
||||||
onClick={() => {
|
|
||||||
if (isGameTile)
|
|
||||||
setTarget({ show: true, x, y });
|
|
||||||
}}
|
|
||||||
onMouseEnter={() => setTargetPreview(e => ({...e, newX: x, newY: y, shouldShow: isGameTile}))}
|
|
||||||
></div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}, [x, y, hits.length]);
|
||||||
|
|
||||||
// handle visibility and position change of targetPreview
|
// handle visibility and position change of targetPreview
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -150,33 +82,23 @@ function App() {
|
||||||
}
|
}
|
||||||
}, [targetPreview.shouldShow, targetPreview.newX, targetPreview.newY]);
|
}, [targetPreview.shouldShow, targetPreview.newX, targetPreview.newY]);
|
||||||
|
|
||||||
for (let i = 1; i <= 6; i++) {
|
|
||||||
shipElems.push(
|
|
||||||
<div key={i} className={`ship s${i}`} style={{'--x': i+3} as CSSProperties}>
|
|
||||||
<img src={`/svgs/${i}.svg`} alt={`${i}.svg`}/>
|
|
||||||
</div>);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<header className="App-header">
|
<header className="App-header">
|
||||||
<div id="game-frame" style={{'--i': count} as CSSProperties}>
|
<div id="game-frame" style={{'--i': count} as CSSProperties}>
|
||||||
{/* Bordes */}
|
{/* Bordes */}
|
||||||
{ borderTiles }
|
<BorderTiles count={count} targets={{setTarget, setTargetPreview}} />
|
||||||
|
|
||||||
{/* Collumn lettes and row numbers */}
|
{/* Collumn lettes and row numbers */}
|
||||||
{elems2.map((obj, i) =>
|
<Labeling count={count} />
|
||||||
<span key={i} className={`${obj.field} r1`} style={{'--x': obj.x, '--y': obj.y} as CSSProperties}>{obj.field}</span>
|
|
||||||
)}
|
|
||||||
{ hitSVGs }
|
|
||||||
|
|
||||||
{/* Ships */}
|
{/* Ships */}
|
||||||
{/* { shipElems } */}
|
{/* <Ships /> */}
|
||||||
|
|
||||||
|
<HitElems hits={hits} />
|
||||||
|
|
||||||
{/* Fog images */}
|
{/* Fog images */}
|
||||||
{/* <img className='fog-left' src={`/fog/fog2.png`} alt={`fog1.png`} />
|
{/* <FogImages /> */}
|
||||||
<img className='fog-right' src={`/fog/fog2.png`} alt={`fog1.png`} />
|
|
||||||
<img className='fog-middle' src={`/fog/fog4.png`} alt={`fog4.png`} /> */}
|
|
||||||
<div className={`hit-svg target ${target.show ? 'show' : ''}`} style={{'--x': target.x+1, '--y': target.y+1} as CSSProperties}>
|
<div className={`hit-svg target ${target.show ? 'show' : ''}`} style={{'--x': target.x+1, '--y': target.y+1} as CSSProperties}>
|
||||||
<FontAwesomeIcon icon={faCrosshairs} />
|
<FontAwesomeIcon icon={faCrosshairs} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -184,9 +106,9 @@ function App() {
|
||||||
<FontAwesomeIcon icon={faCrosshairs} />
|
<FontAwesomeIcon icon={faCrosshairs} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p>
|
{/* <p>
|
||||||
Edit <code>src/App.tsx</code> and save to reload.
|
Edit <code>src/App.tsx</code> and save to reload.
|
||||||
</p>
|
</p> */}
|
||||||
<a
|
<a
|
||||||
className="App-link"
|
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"
|
href="https://www.freepik.com/free-vector/militaristic-ships-set-navy-ammunition-warship-submarine-nuclear-battleship-float-cruiser-trawler-gunboat-frigate-ferry_10704121.htm"
|
||||||
|
|
45
frontend/src/components/BorderTiles.tsx
Normal file
45
frontend/src/components/BorderTiles.tsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { CSSProperties } from 'react';
|
||||||
|
import { borderCN, cornerCN, fieldIndex } from '../helpers';
|
||||||
|
import { TargetPreviewType, TargetType } from '../interfaces';
|
||||||
|
|
||||||
|
function BorderTiles({count, targets: {setTarget, setTargetPreview}}: {count: number, targets: {setTarget: React.Dispatch<React.SetStateAction<TargetType>>, setTargetPreview: React.Dispatch<React.SetStateAction<TargetPreviewType>>}}) {
|
||||||
|
let tilesProperties: {
|
||||||
|
key: number,
|
||||||
|
isGameTile: boolean,
|
||||||
|
classNameString: string,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
for (let y = 0; y < count+2; y++) {
|
||||||
|
for (let x = 0; x < count+2; x++) {
|
||||||
|
const key = fieldIndex(count, x, y);
|
||||||
|
const cornerReslt = cornerCN(count, x, y);
|
||||||
|
const borderType = cornerReslt ? cornerReslt : borderCN(count, x, y);
|
||||||
|
const isGameTile = x > 0 && x < count+1 && y > 0 && y < count+1;
|
||||||
|
const classNames = ['border-tile'];
|
||||||
|
if (borderType)
|
||||||
|
classNames.push('edge', borderType);
|
||||||
|
if (isGameTile)
|
||||||
|
classNames.push('game-tile');
|
||||||
|
const classNameString = classNames.join(' ')
|
||||||
|
tilesProperties.push({key, classNameString, isGameTile, x, y})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return <>
|
||||||
|
{tilesProperties.map(({key, classNameString, isGameTile, x, y}) => {
|
||||||
|
return <div
|
||||||
|
key={key}
|
||||||
|
className={classNameString}
|
||||||
|
style={{'--x': (x + 1), '--y': (y + 1)} as CSSProperties}
|
||||||
|
onClick={() => {
|
||||||
|
if (isGameTile)
|
||||||
|
setTarget({ show: true, x, y });
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setTargetPreview(e => ({...e, newX: x, newY: y, shouldShow: isGameTile}))}
|
||||||
|
></div>
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BorderTiles;
|
9
frontend/src/components/FogImages.tsx
Normal file
9
frontend/src/components/FogImages.tsx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
function FogImages() {
|
||||||
|
return <>
|
||||||
|
<img className='fog-left' src={`/fog/fog2.png`} alt={`fog1.png`} />
|
||||||
|
<img className='fog-right' src={`/fog/fog2.png`} alt={`fog1.png`} />
|
||||||
|
<img className='fog-middle' src={`/fog/fog4.png`} alt={`fog4.png`} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FogImages;
|
17
frontend/src/components/HitElems.tsx
Normal file
17
frontend/src/components/HitElems.tsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { faBurst, faXmark } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { CSSProperties } from 'react';
|
||||||
|
import { HitType } from '../interfaces';
|
||||||
|
|
||||||
|
function HitElems({hits}: {hits: HitType[]}) {
|
||||||
|
|
||||||
|
return <>
|
||||||
|
{hits.map(({hit, x, y}, i) =>
|
||||||
|
<div key={i} className='hit-svg' style={{'--x': x, '--y': y} as CSSProperties}>
|
||||||
|
<FontAwesomeIcon icon={hit ? faBurst : faXmark} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HitElems;
|
29
frontend/src/components/Labeling.tsx
Normal file
29
frontend/src/components/Labeling.tsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { CSSProperties } from 'react'
|
||||||
|
import { fieldIndex } from '../helpers';
|
||||||
|
import { FieldType } from '../interfaces';
|
||||||
|
|
||||||
|
function Labeling({count}: {count: number}) {
|
||||||
|
let elems: (FieldType & {
|
||||||
|
orientation: string
|
||||||
|
})[] = [];
|
||||||
|
for (let x = 0; x < count; x++) {
|
||||||
|
elems.push(
|
||||||
|
// Up
|
||||||
|
{field: String.fromCharCode(65+x), x: x+2, y: 1, orientation: 'up'},
|
||||||
|
// Left
|
||||||
|
{field: (x+1).toString(), x: 1, y: x+2, orientation: 'left'},
|
||||||
|
// Bottom
|
||||||
|
{field: String.fromCharCode(65+x), x: x+2, y: count+2, orientation: 'bottom'},
|
||||||
|
// Right
|
||||||
|
{field: (x+1).toString(), x: count+2, y: x+2, orientation: 'right'}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
elems = elems.sort((a, b) => fieldIndex(count, a.x, a.y)-fieldIndex(count, b.x, b.y));
|
||||||
|
return <>
|
||||||
|
{elems.map(({field, x, y, orientation}, i) =>
|
||||||
|
<span key={i} className={`label ${orientation} ${field}`} style={{'--x': x, '--y': y} as CSSProperties}>{field}</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Labeling;
|
19
frontend/src/components/Ships.tsx
Normal file
19
frontend/src/components/Ships.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { CSSProperties } from 'react'
|
||||||
|
|
||||||
|
function Ships() {
|
||||||
|
let shipIndexes: number[] = [];
|
||||||
|
|
||||||
|
for (let i = 1; i <= 6; i++) {
|
||||||
|
shipIndexes.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
{shipIndexes.map(i =>
|
||||||
|
<div key={i} className={`ship s${i}`} style={{'--x': i+3} as CSSProperties}>
|
||||||
|
<img src={`/svgs/${i}.svg`} alt={`${i}.svg`}/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Ships;
|
52
frontend/src/helpers.ts
Normal file
52
frontend/src/helpers.ts
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import { HitType, HitDispatchType } from "./interfaces";
|
||||||
|
|
||||||
|
export const hitReducer = (formObject: HitType[], action: HitDispatchType) => {
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case 'fireMissle': {
|
||||||
|
const result = [...formObject, action.payload];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return formObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const fieldIndex = (count: number, x: number, y: number) => y*(count+2)+x;
|
||||||
|
export const cornerCN = (count: number, x: number, y: number) => {
|
||||||
|
if (x === 0 && y === 0)
|
||||||
|
return 'left-top-corner';
|
||||||
|
if (x === count+1 && y === 0)
|
||||||
|
return 'right-top-corner';
|
||||||
|
if (x === 0 && y === count+1)
|
||||||
|
return 'left-bottom-corner';
|
||||||
|
if (x === count+1 && y === count+1)
|
||||||
|
return 'right-bottom-corner';
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
export const borderCN = (count: number, x: number, y: number) => {
|
||||||
|
if (x === 0)
|
||||||
|
return 'left';
|
||||||
|
if (y === 0)
|
||||||
|
return 'top';
|
||||||
|
if (x === count+1)
|
||||||
|
return 'right';
|
||||||
|
if (y === count+1)
|
||||||
|
return 'bottom';
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
export const initlialTarget = {
|
||||||
|
show: false,
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
export const initlialTargetPreview = {
|
||||||
|
newX: 0,
|
||||||
|
newY: 0,
|
||||||
|
shouldShow: false,
|
||||||
|
appearOK: false,
|
||||||
|
eventReady: true,
|
||||||
|
show: false,
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
|
@ -1,3 +1,8 @@
|
||||||
|
export interface TargetType {
|
||||||
|
show: boolean,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
};
|
||||||
export interface TargetPreviewType {
|
export interface TargetPreviewType {
|
||||||
newX: number,
|
newX: number,
|
||||||
newY: number,
|
newY: number,
|
||||||
|
@ -7,4 +12,18 @@ export interface TargetPreviewType {
|
||||||
show: boolean,
|
show: boolean,
|
||||||
x: number,
|
x: number,
|
||||||
y: number
|
y: number
|
||||||
}
|
};
|
||||||
|
export interface FieldType {
|
||||||
|
field: string,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
};
|
||||||
|
export interface HitType {
|
||||||
|
hit: boolean,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface fireMissle { type: 'fireMissle', payload: { x: number, y: number, hit: boolean } };
|
||||||
|
|
||||||
|
export type HitDispatchType = fireMissle;
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
grid-template-columns: .75fr repeat(12, 1fr) .75fr;
|
grid-template-columns: .75fr repeat(12, 1fr) .75fr;
|
||||||
// grid-gap: 1px solid blue;
|
// grid-gap: 1px solid blue;
|
||||||
|
|
||||||
> .r1 {
|
> .label {
|
||||||
grid-column: var(--x);
|
grid-column: var(--x);
|
||||||
grid-row: var(--y);
|
grid-row: var(--y);
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,7 @@
|
||||||
// justify-content: center;
|
// justify-content: center;
|
||||||
border: 1px solid yellow;
|
border: 1px solid yellow;
|
||||||
grid-row: var(--x);
|
grid-row: var(--x);
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue