Big rework for mutliple different events

This commit is contained in:
aronmal 2023-01-14 16:28:03 +01:00
parent 1c22ffb829
commit 3d21464165
Signed by: aronmal
GPG key ID: 816B7707426FC612
9 changed files with 163 additions and 222 deletions

View file

@ -1,24 +1,24 @@
import { CSSProperties, Dispatch, SetStateAction } from 'react'; import { CSSProperties, Dispatch, SetStateAction } from 'react';
import { borderCN, cornerCN, fieldIndex, isHit } from '../helpers'; import { borderCN, cornerCN, fieldIndex } from '../helpers';
import { HitDispatchType, HitType, LastLeftTileType, TargetPreviewPosType, TargetType } from '../interfaces'; import { LastLeftTileType, TargetPreviewPosType } from '../interfaces';
function BorderTiles({ props: { count, setTarget, setTargetPreviewPos, hits, DispatchHits, setLastLeftTile } }: { type TilesType = {
key: number,
isGameTile: boolean,
classNameString: string,
x: number,
y: number
}
function BorderTiles({ props: { count, settingTarget, setTargetPreviewPos, setLastLeftTile } }: {
props: { props: {
count: number, count: number,
setTarget: Dispatch<SetStateAction<TargetType>>, settingTarget: (isGameTile: boolean, x: number, y: number) => void,
setTargetPreviewPos: Dispatch<SetStateAction<TargetPreviewPosType>>, setTargetPreviewPos: Dispatch<SetStateAction<TargetPreviewPosType>>,
hits: HitType[],
DispatchHits: Dispatch<HitDispatchType>,
setLastLeftTile: Dispatch<SetStateAction<LastLeftTileType>> setLastLeftTile: Dispatch<SetStateAction<LastLeftTileType>>
} }
}) { }) {
let tilesProperties: { let tilesProperties: TilesType[] = [];
key: number,
isGameTile: boolean,
classNameString: string,
x: number,
y: number
}[] = [];
for (let y = 0; y < count + 2; y++) { for (let y = 0; y < count + 2; y++) {
for (let x = 0; x < count + 2; x++) { for (let x = 0; x < count + 2; x++) {
@ -41,20 +41,7 @@ function BorderTiles({ props: { count, setTarget, setTargetPreviewPos, hits, Dis
key={key} key={key}
className={classNameString} className={classNameString}
style={{ '--x': x, '--y': y } as CSSProperties} style={{ '--x': x, '--y': y } as CSSProperties}
onClick={() => { onClick={() => settingTarget(isGameTile, x, y)}
if (!isGameTile || isHit(hits, x, y).length)
return;
setTargetPreviewPos(e => ({ ...e, shouldShow: false }))
setTarget(t => {
if (t.x === x && t.y === y && t.show) {
DispatchHits({ type: 'fireMissle', payload: { hit: (x + y) % 2 !== 0, x, y } });
return { show: false, x, y };
} else {
return { show: true, x, y };
}
});
}}
onMouseEnter={() => setTargetPreviewPos({ x, y, shouldShow: isGameTile })} onMouseEnter={() => setTargetPreviewPos({ x, y, shouldShow: isGameTile })}
onMouseLeave={() => setLastLeftTile({ x, y })} onMouseLeave={() => setLastLeftTile({ x, y })}
></div> ></div>

View file

@ -14,10 +14,9 @@ function Gamefield() {
targets, targets,
eventBar, eventBar,
setLastLeftTile, setLastLeftTile,
setTarget, settingTarget,
setTargetPreviewPos, setTargetPreviewPos,
hits, hits
DispatchHits
} = useGameEvent(count); } = useGameEvent(count);
return ( return (
@ -25,7 +24,7 @@ function Gamefield() {
{/* <Bluetooth /> */} {/* <Bluetooth /> */}
<div id="game-frame" style={{ '--i': count } as CSSProperties}> <div id="game-frame" style={{ '--i': count } as CSSProperties}>
{/* Bordes */} {/* Bordes */}
<BorderTiles props={{ count, setTarget, setTargetPreviewPos, hits, DispatchHits, setLastLeftTile }} /> <BorderTiles props={{ count, settingTarget, setTargetPreviewPos, setLastLeftTile }} />
{/* Collumn lettes and row numbers */} {/* Collumn lettes and row numbers */}
<Labeling count={count} /> <Labeling count={count} />
@ -38,6 +37,7 @@ function Gamefield() {
{/* Fog images */} {/* Fog images */}
{/* <FogImages /> */} {/* <FogImages /> */}
{targets} {targets}
{/* <span id='dev-debug' style={{gridArea: '1 / 12 / 1 / 15', backgroundColor: 'red', zIndex: 3} as CSSProperties}>Debug</span> */}
</div> </div>
{eventBar} {eventBar}
</div> </div>

View file

@ -2,32 +2,34 @@ import { faCrosshairs } from '@fortawesome/pro-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'; import classNames from 'classnames';
import { TargetType } from '../interfaces';
import { faRadar } from '@fortawesome/pro-thin-svg-icons'; import { faRadar } from '@fortawesome/pro-thin-svg-icons';
function Target({ props: { function GamefieldPointer({ props: {
preview, preview,
type,
edges,
imply,
x, x,
y, y,
show show,
type,
edges,
imply
} }: { } }: {
props: { props: {
preview?: boolean, preview: boolean,
x: number,
y: number,
show: boolean,
type: string, type: string,
edges: string[], edges: string[],
imply: boolean, imply: boolean,
} & TargetType }
}) { }) {
const isRadar = type === 'radar' const isRadar = type === 'radar'
const style = !isRadar ? { '--x': x, '--y': y } : { '--x1': x - 1, '--x2': x + 2, '--y1': y - 1, '--y2': y + 2 } const style = !(isRadar && !edges.filter(s => s).length) ? { '--x': x, '--y': y } : { '--x1': x - 1, '--x2': x + 2, '--y1': y - 1, '--y2': y + 2 }
return ( return (
<div className={classNames('hit-svg', preview ? 'target-preview' : 'target', type, { show: show }, ...edges, { edge: !isRadar || !preview }, { imply: imply })} style={style as CSSProperties}> <div className={classNames('hit-svg', { preview: preview }, 'target', type, { show: show }, ...edges, { imply: imply })} style={style as CSSProperties}>
<FontAwesomeIcon icon={!isRadar ? faCrosshairs : faRadar} /> <FontAwesomeIcon icon={!isRadar ? faCrosshairs : faRadar} />
</div> </div>
) )
} }
export default Target export default GamefieldPointer

View file

@ -76,7 +76,7 @@ function Homepage() {
return ( return (
<div id='tiles' style={{ '--columns': params.columns, '--rows': params.rows, '--bg-color-1': colors[count % colors.length], '--bg-color-2': colors[(count + 1) % colors.length] } as CSSProperties}> <div id='tiles' style={{ '--columns': params.columns, '--rows': params.rows, '--bg-color-1': colors[count % colors.length], '--bg-color-2': colors[(count + 1) % colors.length] } as CSSProperties}>
{Array.from(Array(params.quantity)).map((_tile, index) => createTile(index))} {Array.from(Array(params.quantity), (_tile, index) => createTile(index))}
</div> </div>
) )
}, [params, position, active, count]) }, [params, position, active, count])

View file

@ -81,7 +81,7 @@ function Homepage2() {
<div className="center-div"> <div className="center-div">
<h1 className={classNames('headline', (!active ? 'active' : 'inactive'))}>{sentences[count % sentences.length]}</h1> <h1 className={classNames('headline', (!active ? 'active' : 'inactive'))}>{sentences[count % sentences.length]}</h1>
</div> </div>
{Array.from(Array(params.quantity)).map((_tile, index) => createTile(index))} {Array.from(Array(params.quantity), (_tile, index) => createTile(index))}
</div > </div >
) )
}, [params, position, active, action, count]) }, [params, position, active, action, count])

View file

@ -1,152 +1,111 @@
import { useCallback, 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, ItemsType, LastLeftTileType, TargetListType, TargetModifierType, TargetPreviewPosType, TargetPreviewType, TargetType } from '../interfaces'; import { HitType, ItemsType, LastLeftTileType, ModeType, TargetPreviewPosType, TargetType } from '../interfaces';
import Item from './Item'; import Item from './Item';
import Target from './Target'; import GamefieldPointer from './GamefieldPointer';
export const modes = {
none: { xEnable: false, yEnable: false, type: 'none' },
radar: { xEnable: false, yEnable: false, type: 'radar' },
hTorpedo: { xEnable: true, yEnable: false, type: 'torpedo' },
vTorpedo: { xEnable: false, yEnable: true, type: 'torpedo' },
missle: { xEnable: false, yEnable: false, type: 'missle' }
}
function useGameEvent(count: number) { 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);
const [appearOK, setAppearOK] = useState(false); const [appearOK, setAppearOK] = useState(false);
const [targetPreview, setTargetPreview] = useState<TargetPreviewType>(initlialTargetPreview); const [targetPreview, setTargetPreview] = useState<TargetType>(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 [mode, setMode] = useState<[number, string]>([0, ''])
const [targetList, setTargetList] = useState<TargetListType[]>([]) const [mode, setMode] = useState(0)
const [targetPreviewList, setTargetPreviewList] = useState<TargetListType[]>([])
function modXY<T>(e: TargetType, mod: TargetModifierType) { const modes = useMemo<ModeType[]>(() => [
const { show, ...pos } = e { pointerGrid: Array.from(Array(3), () => Array.from(Array(3))), type: 'radar' },
const { target, params } = mod { pointerGrid: Array.from(Array(3), () => Array.from(Array(1))), type: 'htorpedo' },
return { { pointerGrid: Array.from(Array(1), () => Array.from(Array(3))), type: 'vhtorpedo' },
target: { { pointerGrid: [[{ x: 0, y: 0 }]], type: 'missle' }
show, ], [])
x: pos.x + (target.x ?? 0),
y: pos.y + (target.y ?? 0)
},
params
}
}
const isSet = useCallback((x: number, y: number) => targetList.filter(({ target }) => x === target.x && y === target.y).length && target.show, [targetList, target]) const settingTarget = useCallback((isGameTile: boolean, x: number, y: number) => {
if (!isGameTile || isHit(hits, x, y).length)
const scopeGrid = useMemo(() => { return;
const { xEnable, yEnable } = modes[mode] setTargetPreviewPos(e => ({ ...e, shouldShow: false }))
const matrix: TargetModifierType[][] = [] setTarget(t => {
let y = 0 if (t.x === x && t.y === y && t.show) {
let x = 0 DispatchHits({ type: 'fireMissle', payload: { hit: (x + y) % 2 !== 0, x, y } });
const yLength = (yEnable ? 2 : 0) return { preview: false, show: false, x, y };
const xLength = (xEnable ? 2 : 0) } else {
for (let i = 0; i <= yLength; i++) { return { preview: false, show: true, x, y };
for (let i2 = 0; i2 <= xLength; i2++) {
y = i + (yEnable ? -1 : 0);
x = i2 + (xEnable ? -1 : 0);
(matrix[i] ??= [])[i2] = {
target: {
x,
y,
},
params: {
edges: [
i2 === 0 ? 'left' : '',
i2 === xLength ? 'right' : '',
i === 0 ? 'top' : '',
i === yLength ? 'bottom' : '',
],
imply: false
}
}
} }
}
const fields = matrix.reduce((prev, curr) => [...prev, ...curr], [])
return fields
}, [mode])
const Targets = useCallback((targets: TargetListType[], preview?: boolean) => {
const { type } = modes[mode]
return targets.map(({ target, params }, i) => <Target key={i} props={{ type, preview, ...params, ...target }} />)
}, [mode])
useEffect(() => {
const result = scopeGrid.map(e => modXY(target, e))
.filter(({ target: { x, y } }) => {
const border = [
x < 2,
x > count,
y < 2,
y > count,
].reduce((prev, curr) => prev || curr, false)
return !border
}).map(field => {
const { target, params } = field
const { x, y } = target
if (isHit(hits, x, y).length)
return { ...field, params: { ...params, imply: true } }
return field
})
setTargetList(e => {
if (JSON.stringify(e) === JSON.stringify(result))
return e
return result
}) })
}, [scopeGrid, target, count, hits]); }, [])
useEffect(() => { const targetList = useCallback((target: TargetType) => {
const result = scopeGrid.map(e => modXY(targetPreview, e)) const { pointerGrid, type } = modes[mode]
.filter(({ target: { x, y } }) => { const xLength = pointerGrid.length
const border = [ const yLength = pointerGrid[0].length
x < 2, const { x: targetX, y: targetY } = target
x > count + 1, return pointerGrid.map((arr, i) => {
y < 2, return arr.map((_, i2) => {
y > count + 1, const relativeX = -Math.floor(xLength / 2) + i;
].reduce((prev, curr) => prev || curr, false) const relativeY = -Math.floor(yLength / 2) + i2;
return !border const x = targetX + (relativeX ?? 0)
}).map(field => { const y = targetY + (relativeY ?? 0)
const { target, params } = field return {
const { x, y } = target x,
if (isHit(hits, x, y).length || isSet(x, y)) y,
return { ...field, params: { ...params, imply: true } } type,
return field edges: [
i === 0 ? 'left' : '',
i === xLength - 1 ? 'right' : '',
i2 === 0 ? 'top' : '',
i2 === yLength - 1 ? 'bottom' : '',
]
}
}) })
if (!targetPreviewPos.shouldShow)
return
setTargetPreviewList(e => {
if (JSON.stringify(e) === JSON.stringify(result))
return e
return result
}) })
}, [scopeGrid, targetPreview, count, hits, isSet, targetPreviewPos.shouldShow]); .reduce((prev, curr) => [...prev, ...curr], [])
}, [mode, modes])
const isSet = useCallback((x: number, y: number) => !!targetList(target).filter(field => x === field.x && y === field.y).length && target.show, [target, targetList])
const composeTargetTiles = useCallback((target: TargetType) => {
const { preview, show } = target
const result = targetList(target).map(({ x, y, type, edges }) => {
return {
preview,
x,
y,
show,
type,
edges,
imply: !!isHit(hits, x, y).length || (!!isSet(x, y) && preview),
// isborder: x < 2 || x > count + 1 || y < 2 || y > count + 1
}
})
// .filter(({ isborder }) => !isborder)
return result
}, [count, hits, isSet, targetList])
// if (!targetPreviewPos.shouldShow)
// return
// 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;
// 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 && target.show
if (show && !appearOK) if (show && !appearOK)
setTargetPreview(e => ({ ...e, show: false })); setTargetPreview(e => ({ ...e, show: false }));
if (!show && targetPreviewPos.shouldShow && eventReady && appearOK && !isHit(hits, x, y).length && !hasLeft) if (!show && targetPreviewPos.shouldShow && eventReady && appearOK && !isHit(hits, x, y).length && !isSet && !hasLeft)
setTargetPreview(e => ({ ...e, show: true })); setTargetPreview(e => ({ ...e, show: true }));
}, [targetPreview, targetPreviewPos.shouldShow, hits, eventReady, appearOK, lastLeftTile]) }, [targetPreview, targetPreviewPos.shouldShow, hits, eventReady, appearOK, lastLeftTile, target])
// enable targetPreview event again after 200 mil. sec. // enable targetPreview event again after 200 mil. sec.
useEffect(() => { useEffect(() => {
const { x: newX, y: newY } = targetPreviewPos;
setEventReady(false); setEventReady(false);
if (targetPreview.show || !appearOK) if (targetPreview.show || !appearOK)
return; return;
const autoTimeout = setTimeout(() => { const autoTimeout = setTimeout(() => {
setTargetPreview(e => ({ ...e, x: newX, y: newY })); setTargetPreview(e => ({ ...e, x: targetPreviewPos.x, y: targetPreviewPos.y }));
setEventReady(true); setEventReady(true);
setAppearOK(true); setAppearOK(true);
}, 300); }, 300);
@ -155,14 +114,14 @@ function useGameEvent(count: number) {
return () => { return () => {
clearTimeout(autoTimeout); clearTimeout(autoTimeout);
} }
}, [targetPreviewPos, targetPreview.show, appearOK]); }, [appearOK, targetPreview.show, targetPreviewPos.x, targetPreviewPos.y]);
// 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
const autoTimeout = setTimeout(() => { const autoTimeout = setTimeout(() => {
setAppearOK(!targetPreview.show) setAppearOK(!targetPreview.show)
}, 600); }, targetPreview.show ? 500 : 300);
// or abort if movement is repeated early // or abort if movement is repeated early
return () => { return () => {
@ -170,23 +129,23 @@ function useGameEvent(count: number) {
} }
}, [targetPreview.show]); }, [targetPreview.show]);
const targets = useMemo(() => <> const targets = useMemo(() => [
{Targets(targetPreviewList, true)} ...composeTargetTiles(target).map((props, i) => <GamefieldPointer key={'t' + i} props={props} />),
{Targets(targetList)} ...composeTargetTiles(targetPreview).map((props, i) => <GamefieldPointer key={'p' + i} props={props} />)
</>, [Targets, targetList, targetPreviewList]) ], [composeTargetTiles, target, targetPreview])
const eventBar = useMemo(() => { const eventBar = useMemo(() => {
const items: ItemsType[] = [ const items: ItemsType[] = [
{ icon: 'burger-menu', text: 'Menu' }, { icon: 'burger-menu', text: 'Menu' },
{ icon: 'radar', text: 'Radar scan', type: 'radar', amount: 1 }, { icon: 'radar', text: 'Radar scan', mode: 0, amount: 1 },
{ icon: 'missle', text: 'Fire torpedo', type: 'hTorpedo', amount: 1 }, { icon: 'missle', text: 'Fire torpedo', mode: 1, amount: 1 },
{ icon: 'scope', text: 'Fire missle', type: 'missle' }, { icon: 'scope', text: 'Fire missle', mode: 2 },
{ icon: 'gear', text: 'Settings' } { icon: 'gear', text: 'Settings' }
] ]
return ( return (
<div className='event-bar'> <div className='event-bar'>
{items.map((e, i) => ( {items.map((e, i) => (
<Item key={i} props={{ ...e, callback: () => { setMode(e.type as any); setTarget(e => ({ ...e, show: false })) } }} /> <Item key={i} props={{ ...e, callback: () => { e.mode !== undefined ? setMode(e.mode) : {}; setTarget(e => ({ ...e, show: false })) } }} />
))} ))}
</div> </div>
) )
@ -195,10 +154,9 @@ function useGameEvent(count: number) {
targets, targets,
eventBar, eventBar,
setLastLeftTile, setLastLeftTile,
setTarget, settingTarget,
setTargetPreviewPos, setTargetPreviewPos,
hits, hits
DispatchHits
} }
} }

View file

@ -40,11 +40,13 @@ export const initlialLastLeftTile = {
y: 0 y: 0
}; };
export const initlialTarget = { export const initlialTarget = {
preview: false,
show: false, show: false,
x: 2, x: 2,
y: 2 y: 2
}; };
export const initlialTargetPreview = { export const initlialTargetPreview = {
preview: true,
show: false, show: false,
x: 2, x: 2,
y: 2 y: 2

View file

@ -1,15 +1,9 @@
import { modes } from "./components/useGameEvent";
export type LastLeftTileType = { export type LastLeftTileType = {
x: number, x: number,
y: number y: number
} }
export type TargetType = { export type TargetType = {
show: boolean, preview: boolean,
x: number,
y: number
};
export type TargetPreviewType = {
show: boolean, show: boolean,
x: number, x: number,
y: number y: number
@ -20,30 +14,19 @@ export type TargetPreviewPosType = {
y: number y: number
} }
export type TargetListType = { export type TargetListType = {
target: { x: number,
show: boolean, y: number,
x: number, type: string,
y: number, edges: string[]
},
params: {
edges: string[],
imply: boolean
}
} }
export type TargetModifierType = { export type ModeType = {
target: { pointerGrid: any[][],
x: number, type: string
y: number,
},
params: {
edges: string[],
imply: boolean
}
} }
export type ItemsType = { export type ItemsType = {
icon: string, icon: string,
text: string, text: string,
type?: keyof typeof modes, mode?: number,
amount?: number, amount?: number,
} }
export type FieldType = { export type FieldType = {

View file

@ -159,35 +159,42 @@ body {
--color: red; --color: red;
color: var(--color); color: var(--color);
opacity: 0; opacity: 0;
&.preview {
--color: orange;
}
} }
&.target-preview { &.preview {
--color: orange;
color: var(--color);
opacity: 0; opacity: 0;
@include transition(.5s); @include transition(.5s);
} }
&.radar { &.radar {
--color: limegreen; --color: limegreen;
border: 5px solid var(--color);
grid-column-start: var(--x1) !important; svg {
grid-column-end: var(--x2) !important; opacity: 0;
grid-row-start: var(--y1) !important; }
grid-row-end: var(--y2) !important;
&:not(.left):not(.right):not(.top):not(.bottom) {
// border: 5px solid var(--color);
grid-area: var(--y1) /var(--x1) /var(--y2) /var(--x2);
svg {
opacity: 1;
padding: 12.5%;
}
}
&.imply { &.imply {
box-shadow: 0 0 6px 6px #fff4; box-shadow: 0 0 6px 6px #fff4;
} }
&.target-preview { &.preview {
--color: lawngreen; --color: lawngreen;
background-color: #0001; background-color: #0001;
border: 5px dashed var(--color); // border: 5px dashed var(--color);
}
svg {
padding: 12.5%;
} }
} }
@ -215,20 +222,22 @@ body {
border-bottom: 2px solid var(--color); border-bottom: 2px solid var(--color);
} }
&.edge.imply.left { &.imply {
border-left: 2px dashed white; &.left {
} border-left: 2px dashed white;
}
&.edge.imply.right { &.right {
border-right: 2px dashed white; border-right: 2px dashed white;
} }
&.edge.imply.top { &.top {
border-top: 2px dashed white; border-top: 2px dashed white;
} }
&.imply.bottom { &.bottom {
border-bottom: 2px dashed white; border-bottom: 2px dashed white;
}
} }
&.left.top { &.left.top {