diff --git a/leaky-ships/components/Item.tsx b/leaky-ships/components/Item.tsx
index dcac540..be562dd 100644
--- a/leaky-ships/components/Item.tsx
+++ b/leaky-ships/components/Item.tsx
@@ -1,14 +1,14 @@
import React from 'react'
-function Item({ props: { icon, text, cllFn } }: {
+function Item({ props: { icon, text, callback } }: {
props: {
icon: string,
text: string,
- cllFn: () => void,
+ callback: () => void
}
}) {
return (
-
+
{text}
diff --git a/leaky-ships/components/Target.tsx b/leaky-ships/components/Target.tsx
index 2e83f4a..085ad8d 100644
--- a/leaky-ships/components/Target.tsx
+++ b/leaky-ships/components/Target.tsx
@@ -1,10 +1,11 @@
import { faCrosshairs } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { CSSProperties } from 'react';
+import classNames from 'classnames';
-function Target({ preview, target: { x, y, show } }: { preview?: boolean, target: { x: number, y: number, show: boolean } }) {
+function Target({ props: { preview, type, edges }, target: { x, y, show } }: { props: { preview?: boolean, type: string, edges: string[] }, target: { x: number, y: number, show: boolean } }) {
return (
-
+
)
diff --git a/leaky-ships/components/useGameEvent.tsx b/leaky-ships/components/useGameEvent.tsx
index 15a67a3..2c05269 100644
--- a/leaky-ships/components/useGameEvent.tsx
+++ b/leaky-ships/components/useGameEvent.tsx
@@ -1,10 +1,10 @@
-import { useEffect, useMemo, useReducer, useState } from 'react';
+import { useCallback, useEffect, useMemo, useReducer, useState } from 'react';
import { hitReducer, initlialLastLeftTile, initlialTarget, initlialTargetPreview, initlialTargetPreviewPos, isHit } from '../helpers';
import { HitType, LastLeftTileType, TargetPreviewPosType, TargetPreviewType, TargetType } from '../interfaces';
import Item from './Item';
import Target from './Target';
-function useGameEvent() {
+function useGameEvent(count: number) {
const [lastLeftTile, setLastLeftTile] = useState
(initlialLastLeftTile);
const [target, setTarget] = useState(initlialTarget);
const [eventReady, setEventReady] = useState(false);
@@ -12,20 +12,118 @@ function useGameEvent() {
const [targetPreview, setTargetPreview] = useState(initlialTargetPreview);
const [targetPreviewPos, setTargetPreviewPos] = useState(initlialTargetPreviewPos);
const [hits, DispatchHits] = useReducer(hitReducer, [] as HitType[]);
+ const [mode, setMode] = useState('none')
+ const [targetList, setTargetList] = useState<{
+ show: boolean;
+ x: number;
+ y: number;
+ edges: string[];
+ }[]>([])
+ const [targetPreviewList, setTargetPreviewList] = useState<{
+ show: boolean;
+ x: number;
+ y: number;
+ edges: string[];
+ }[]>([])
+
+ const modes = useMemo(() => ({
+ none: { xEnable: true, yEnable: true, type: 'none' },
+ radar: { xEnable: true, yEnable: true, type: 'radar' },
+ hTorpedo: { xEnable: true, yEnable: false, type: 'torpedo' },
+ vTorpedo: { xEnable: false, yEnable: true, type: 'torpedo' },
+ missle: { xEnable: false, yEnable: false, type: 'missle' }
+ }), [])
+
+ function modXY(e: { show: boolean, x: number, y: number }, mod: { x: number, y: number, edges: string[] }) {
+ return { show: e.show, x: e.x + (mod.x ?? 0), y: e.y + (mod.y ?? 0), edges: mod.edges }
+ }
+
+ const isSet = useCallback((x: number, y: number) => targetList.filter(target => x === target.x && y === target.y).length && target.show, [targetList, target])
+
+ const scopeGrid = useMemo(() => {
+ const { xEnable, yEnable, type } = modes[mode]
+ const matrix: { x: number, y: number, edges: string[] }[][] = []
+ let y = 0
+ let x = 0
+ const yLength = (yEnable ? 2 : 0)
+ const xLength = (xEnable ? 2 : 0)
+ for (let i = 0; i <= yLength; i++) {
+ for (let i2 = 0; i2 <= xLength; i2++) {
+ y = i + (yEnable ? -1 : 0);
+ x = i2 + (xEnable ? -1 : 0);
+
+ (matrix[i] ??= [])[i2] = {
+ x,
+ y,
+ edges: [
+ i2 === 0 ? 'left' : '',
+ i2 === xLength ? 'right' : '',
+ i === 0 ? 'top' : '',
+ i === yLength ? 'bottom' : '',
+ ]
+ }
+ }
+ }
+ const fields = matrix.reduce((prev, curr) => [...prev, ...curr], [])
+ return { fields, type }
+ }, [modes, mode])
+
+ const Targets = useCallback((targets: { show: boolean, x: number, y: number, edges: string[] }[], preview?: boolean) => {
+ const { type } = scopeGrid
+ return targets.map(({ edges, ...target }, i) => )
+ }, [scopeGrid, mode])
+
+ useEffect(() => {
+ const { fields } = scopeGrid
+ const result = fields.map(e => modXY(target, e))
+ .filter(({ x, y }) => {
+ const border = [
+ x < 2,
+ x > count,
+ y < 2,
+ y > count,
+ ].reduce((prev, curr) => prev || curr, false)
+ // console.log(!isHit(hits, x, y).length, !borders)
+ return !isHit(hits, x, y).length && !border
+ })
+ setTargetList(e => {
+ if (JSON.stringify(e) === JSON.stringify(result))
+ return e
+ return result
+ })
+ }, [scopeGrid, target, count, hits]);
+
+ useEffect(() => {
+ const { fields } = scopeGrid
+ const result = fields.map(e => modXY(targetPreview, e))
+ .filter(({ x, y }) => {
+ const border = [
+ x < 2,
+ x > count + 1,
+ y < 2,
+ y > count + 1,
+ ].reduce((prev, curr) => prev || curr, false)
+ // console.log(!isHit(hits, x, y).length, !isSet(x, y), !borders)
+ return !isHit(hits, x, y).length && !isSet(x, y) && !border
+ })
+ setTargetPreviewList(e => {
+ if (JSON.stringify(e) === JSON.stringify(result))
+ return e
+ return result
+ })
+ }, [scopeGrid, targetPreview, count, hits, isSet]);
// handle visibility and position change of targetPreview
useEffect(() => {
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)
+ if (!show && targetPreviewPos.shouldShow && eventReady && appearOK && !isHit(hits, x, y).length && !hasLeft)
setTargetPreview(e => ({ ...e, show: true }));
- }, [targetPreview, hits, eventReady, appearOK, lastLeftTile])
+ }, [targetPreview, targetPreviewPos.shouldShow, hits, eventReady, appearOK, lastLeftTile])
// enable targetPreview event again after 200 mil. sec.
useEffect(() => {
@@ -37,7 +135,7 @@ function useGameEvent() {
setTargetPreview(e => ({ ...e, x: newX, y: newY }));
setEventReady(true);
setAppearOK(true);
- }, 250);
+ }, 300);
// or abort if state has changed early
return () => {
@@ -58,27 +156,27 @@ function useGameEvent() {
}
}, [targetPreview.show]);
- const items = [
- { icon: 'burger-menu', text: 'Menu', cllFn: () => { } },
- { icon: 'radar', text: 'Radar scan', cllFn: () => { } },
- { icon: 'missle', text: 'Fire torpedo', cllFn: () => { } },
- { icon: 'scope', text: 'Fire missle', cllFn: () => { } },
- { icon: 'gear', text: 'Settings', cllFn: () => { } }
- ]
-
- const targets = useMemo(() =>
- <>
-
-
- >, [target, targetPreview])
-
- const eventBar = useMemo(() =>
-
- {items.map((e, i) => (
-
- ))}
-
, [items])
+ const targets = useMemo(() => <>
+ {Targets(targetPreviewList, true)}
+ {Targets(targetList)}
+ >, [Targets, targetList, targetPreviewList])
+ const eventBar = useMemo(() => {
+ const items = [
+ { icon: 'burger-menu', text: 'Menu' },
+ { icon: 'radar', text: 'Radar scan', type: 'radar' },
+ { icon: 'missle', text: 'Fire torpedo', type: 'hTorpedo' },
+ { icon: 'scope', text: 'Fire missle', type: 'missle' },
+ { icon: 'gear', text: 'Settings' }
+ ]
+ return (
+
+ {items.map((e, i) => (
+ - { setMode(e.type as any); setTarget(e => ({ ...e, show: false })) } }} />
+ ))}
+
+ )
+ }, [])
return {
targets,
eventBar,
diff --git a/leaky-ships/package-lock.json b/leaky-ships/package-lock.json
index b9054dc..00f43bd 100644
--- a/leaky-ships/package-lock.json
+++ b/leaky-ships/package-lock.json
@@ -16,6 +16,7 @@
"@types/node": "18.11.18",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.10",
+ "classnames": "^2.3.2",
"eslint": "8.31.0",
"eslint-config-next": "13.1.1",
"next": "13.1.1",
@@ -889,6 +890,11 @@
"node": ">= 6"
}
},
+ "node_modules/classnames": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
+ "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
+ },
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
@@ -4008,6 +4014,11 @@
}
}
},
+ "classnames": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
+ "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
+ },
"client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
diff --git a/leaky-ships/package.json b/leaky-ships/package.json
index 9c9ef1c..539f794 100644
--- a/leaky-ships/package.json
+++ b/leaky-ships/package.json
@@ -17,6 +17,7 @@
"@types/node": "18.11.18",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.10",
+ "classnames": "^2.3.2",
"eslint": "8.31.0",
"eslint-config-next": "13.1.1",
"next": "13.1.1",
diff --git a/leaky-ships/styles/App.scss b/leaky-ships/styles/App.scss
index 5e36fce..73b72ee 100644
--- a/leaky-ships/styles/App.scss
+++ b/leaky-ships/styles/App.scss
@@ -154,14 +154,16 @@ body {
color: red;
}
}
-
+
&.target {
- color: red;
+ --color: red;
+ color: var(--color);
opacity: 0;
}
&.target-preview {
- color: orange;
+ --color: orange;
+ color: var(--color);
opacity: 0;
@include transition(.5s);
}
@@ -169,6 +171,38 @@ body {
&.show {
opacity: 1;
}
+
+ &.left {
+ border-left: 2px solid var(--color);
+ }
+
+ &.right {
+ border-right: 2px solid var(--color);
+ }
+
+ &.top {
+ border-top: 2px solid var(--color);
+ }
+
+ &.bottom {
+ border-bottom: 2px solid var(--color);
+ }
+
+ &.left.top {
+ border-top-left-radius: 8px;
+ }
+
+ &.right.top {
+ border-top-right-radius: 8px;
+ }
+
+ &.left.bottom {
+ border-bottom-left-radius: 8px;
+ }
+
+ &.right.bottom {
+ border-bottom-right-radius: 8px;
+ }
}
.r2 {