Working multiselect/multi trageting
This commit is contained in:
parent
b928510632
commit
b3fdf8dbeb
8 changed files with 183 additions and 39 deletions
|
@ -46,7 +46,7 @@ function BorderTiles({ props: { count, setTarget, setTargetPreviewPos, hits, Dis
|
||||||
return;
|
return;
|
||||||
setTargetPreviewPos(e => ({ ...e, shouldShow: false }))
|
setTargetPreviewPos(e => ({ ...e, shouldShow: false }))
|
||||||
setTarget(t => {
|
setTarget(t => {
|
||||||
if (t.x === x && t.y === y) {
|
if (t.x === x && t.y === y && t.show) {
|
||||||
DispatchHits({ type: 'fireMissle', payload: { hit: (x + y) % 2 !== 0, x, y } });
|
DispatchHits({ type: 'fireMissle', payload: { hit: (x + y) % 2 !== 0, x, y } });
|
||||||
return { show: false, x, y };
|
return { show: false, x, y };
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
|
import { CSSProperties } from 'react';
|
||||||
// import Bluetooth from './Bluetooth';
|
// import Bluetooth from './Bluetooth';
|
||||||
import BorderTiles from './BorderTiles';
|
import BorderTiles from './BorderTiles';
|
||||||
// import FogImages from './FogImages';
|
// import FogImages from './FogImages';
|
||||||
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 Item from './Item';
|
|
||||||
import useGameEvent from './useGameEvent';
|
import useGameEvent from './useGameEvent';
|
||||||
import { CSSProperties } from 'react';
|
|
||||||
|
|
||||||
function Gamefield() {
|
function Gamefield() {
|
||||||
|
|
||||||
|
@ -19,7 +18,7 @@ function Gamefield() {
|
||||||
setTargetPreviewPos,
|
setTargetPreviewPos,
|
||||||
hits,
|
hits,
|
||||||
DispatchHits
|
DispatchHits
|
||||||
} = useGameEvent();
|
} = useGameEvent(count);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id='gamefield'>
|
<div id='gamefield'>
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
function Item({ props: { icon, text, cllFn } }: {
|
function Item({ props: { icon, text, callback } }: {
|
||||||
props: {
|
props: {
|
||||||
icon: string,
|
icon: string,
|
||||||
text: string,
|
text: string,
|
||||||
cllFn: () => void,
|
callback: () => void
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className='item' onClick={cllFn}>
|
<div className='item' onClick={callback}>
|
||||||
<img src={`/assets/${icon}.png`} alt={`${icon}.png`} />
|
<img src={`/assets/${icon}.png`} alt={`${icon}.png`} />
|
||||||
<span>{text}</span>
|
<span>{text}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { faCrosshairs } 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 } from 'react';
|
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 (
|
return (
|
||||||
<div className={`hit-svg target${preview ? '-preview' : ''} ${show ? 'show' : ''}`} style={{ '--x': x, '--y': y } as CSSProperties}>
|
<div className={classNames('hit-svg', preview ? 'target-preview' : 'target', type, { show: show }, ...edges)} style={{ '--x': x, '--y': y } as CSSProperties}>
|
||||||
<FontAwesomeIcon icon={faCrosshairs} />
|
<FontAwesomeIcon icon={faCrosshairs} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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 { hitReducer, initlialLastLeftTile, initlialTarget, initlialTargetPreview, initlialTargetPreviewPos, isHit } from '../helpers';
|
||||||
import { HitType, LastLeftTileType, TargetPreviewPosType, TargetPreviewType, TargetType } from '../interfaces';
|
import { HitType, LastLeftTileType, TargetPreviewPosType, TargetPreviewType, TargetType } from '../interfaces';
|
||||||
import Item from './Item';
|
import Item from './Item';
|
||||||
import Target from './Target';
|
import Target from './Target';
|
||||||
|
|
||||||
function useGameEvent() {
|
function useGameEvent(count: number) {
|
||||||
const [lastLeftTile, setLastLeftTile] = useState<LastLeftTileType>(initlialLastLeftTile);
|
const [lastLeftTile, setLastLeftTile] = useState<LastLeftTileType>(initlialLastLeftTile);
|
||||||
const [target, setTarget] = useState<TargetType>(initlialTarget);
|
const [target, setTarget] = useState<TargetType>(initlialTarget);
|
||||||
const [eventReady, setEventReady] = useState(false);
|
const [eventReady, setEventReady] = useState(false);
|
||||||
|
@ -12,20 +12,118 @@ function useGameEvent() {
|
||||||
const [targetPreview, setTargetPreview] = useState<TargetPreviewType>(initlialTargetPreview);
|
const [targetPreview, setTargetPreview] = useState<TargetPreviewType>(initlialTargetPreview);
|
||||||
const [targetPreviewPos, setTargetPreviewPos] = useState<TargetPreviewPosType>(initlialTargetPreviewPos);
|
const [targetPreviewPos, setTargetPreviewPos] = useState<TargetPreviewPosType>(initlialTargetPreviewPos);
|
||||||
const [hits, DispatchHits] = useReducer(hitReducer, [] as HitType[]);
|
const [hits, DispatchHits] = useReducer(hitReducer, [] as HitType[]);
|
||||||
|
const [mode, setMode] = useState<keyof typeof modes>('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<T>(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) => <Target key={i} props={{ type, preview, edges }} target={target} />)
|
||||||
|
}, [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
|
// handle visibility and position change of targetPreview
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { show, x, y } = targetPreview;
|
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
|
// 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 hasLeft = x === lastLeftTile.x && y === lastLeftTile.y
|
||||||
const isSet = x === target.x && y === target.y
|
|
||||||
|
|
||||||
if (show && !appearOK)
|
if (show && !appearOK)
|
||||||
setTargetPreview(e => ({ ...e, show: false }));
|
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 }));
|
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.
|
// enable targetPreview event again after 200 mil. sec.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -37,7 +135,7 @@ function useGameEvent() {
|
||||||
setTargetPreview(e => ({ ...e, x: newX, y: newY }));
|
setTargetPreview(e => ({ ...e, x: newX, y: newY }));
|
||||||
setEventReady(true);
|
setEventReady(true);
|
||||||
setAppearOK(true);
|
setAppearOK(true);
|
||||||
}, 250);
|
}, 300);
|
||||||
|
|
||||||
// or abort if state has changed early
|
// or abort if state has changed early
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -58,27 +156,27 @@ function useGameEvent() {
|
||||||
}
|
}
|
||||||
}, [targetPreview.show]);
|
}, [targetPreview.show]);
|
||||||
|
|
||||||
|
const targets = useMemo(() => <>
|
||||||
|
{Targets(targetPreviewList, true)}
|
||||||
|
{Targets(targetList)}
|
||||||
|
</>, [Targets, targetList, targetPreviewList])
|
||||||
|
|
||||||
|
const eventBar = useMemo(() => {
|
||||||
const items = [
|
const items = [
|
||||||
{ icon: 'burger-menu', text: 'Menu', cllFn: () => { } },
|
{ icon: 'burger-menu', text: 'Menu' },
|
||||||
{ icon: 'radar', text: 'Radar scan', cllFn: () => { } },
|
{ icon: 'radar', text: 'Radar scan', type: 'radar' },
|
||||||
{ icon: 'missle', text: 'Fire torpedo', cllFn: () => { } },
|
{ icon: 'missle', text: 'Fire torpedo', type: 'hTorpedo' },
|
||||||
{ icon: 'scope', text: 'Fire missle', cllFn: () => { } },
|
{ icon: 'scope', text: 'Fire missle', type: 'missle' },
|
||||||
{ icon: 'gear', text: 'Settings', cllFn: () => { } }
|
{ icon: 'gear', text: 'Settings' }
|
||||||
]
|
]
|
||||||
|
return (
|
||||||
const targets = useMemo(() =>
|
|
||||||
<>
|
|
||||||
<Target target={target} />
|
|
||||||
<Target preview={true} target={targetPreview} />
|
|
||||||
</>, [target, targetPreview])
|
|
||||||
|
|
||||||
const eventBar = useMemo(() =>
|
|
||||||
<div className='event-bar'>
|
<div className='event-bar'>
|
||||||
{items.map((e, i) => (
|
{items.map((e, i) => (
|
||||||
<Item key={i} props={e} />
|
<Item key={i} props={{ ...e, callback: () => { setMode(e.type as any); setTarget(e => ({ ...e, show: false })) } }} />
|
||||||
))}
|
))}
|
||||||
</div>, [items])
|
</div>
|
||||||
|
)
|
||||||
|
}, [])
|
||||||
return {
|
return {
|
||||||
targets,
|
targets,
|
||||||
eventBar,
|
eventBar,
|
||||||
|
|
11
leaky-ships/package-lock.json
generated
11
leaky-ships/package-lock.json
generated
|
@ -16,6 +16,7 @@
|
||||||
"@types/node": "18.11.18",
|
"@types/node": "18.11.18",
|
||||||
"@types/react": "18.0.26",
|
"@types/react": "18.0.26",
|
||||||
"@types/react-dom": "18.0.10",
|
"@types/react-dom": "18.0.10",
|
||||||
|
"classnames": "^2.3.2",
|
||||||
"eslint": "8.31.0",
|
"eslint": "8.31.0",
|
||||||
"eslint-config-next": "13.1.1",
|
"eslint-config-next": "13.1.1",
|
||||||
"next": "13.1.1",
|
"next": "13.1.1",
|
||||||
|
@ -889,6 +890,11 @@
|
||||||
"node": ">= 6"
|
"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": {
|
"node_modules/client-only": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
"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": {
|
"client-only": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
"@types/node": "18.11.18",
|
"@types/node": "18.11.18",
|
||||||
"@types/react": "18.0.26",
|
"@types/react": "18.0.26",
|
||||||
"@types/react-dom": "18.0.10",
|
"@types/react-dom": "18.0.10",
|
||||||
|
"classnames": "^2.3.2",
|
||||||
"eslint": "8.31.0",
|
"eslint": "8.31.0",
|
||||||
"eslint-config-next": "13.1.1",
|
"eslint-config-next": "13.1.1",
|
||||||
"next": "13.1.1",
|
"next": "13.1.1",
|
||||||
|
|
|
@ -156,12 +156,14 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
&.target {
|
&.target {
|
||||||
color: red;
|
--color: red;
|
||||||
|
color: var(--color);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.target-preview {
|
&.target-preview {
|
||||||
color: orange;
|
--color: orange;
|
||||||
|
color: var(--color);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@include transition(.5s);
|
@include transition(.5s);
|
||||||
}
|
}
|
||||||
|
@ -169,6 +171,38 @@ body {
|
||||||
&.show {
|
&.show {
|
||||||
opacity: 1;
|
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 {
|
.r2 {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue