Merge pull request #4 from aronmal/dev

Dev
This commit is contained in:
aronmal 2023-01-13 13:33:25 +01:00 committed by GitHub
commit 6de1784f16
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 181 additions and 79 deletions

View file

@ -1,15 +1,19 @@
import React from 'react' import classNames from 'classnames'
import React, { CSSProperties } from 'react'
function Item({ props: { icon, text, callback } }: { function Item({ props: { icon, text, amount, callback } }: {
props: { props: {
icon: string, icon: string,
text: string, text: string,
amount?: number,
callback: () => void callback: () => void
} }
}) { }) {
return ( return (
<div className='item' onClick={callback}> <div className='item' onClick={callback}>
<img src={`/assets/${icon}.png`} alt={`${icon}.png`} /> <div className={classNames('container', { amount: amount })} style={amount ? { '--amount': JSON.stringify(amount.toString()) } as CSSProperties : {}}>
<img src={`/assets/${icon}.png`} alt={`${icon}.png`} />
</div>
<span>{text}</span> <span>{text}</span>
</div> </div>
) )

View file

@ -15,7 +15,7 @@ function Ships() {
{shipIndexes.map(({ size, index }, i) => { {shipIndexes.map(({ size, index }, i) => {
const filename = `/assets/ship_blue_${size}x${index ? '_' + index : ''}.gif` const filename = `/assets/ship_blue_${size}x${index ? '_' + index : ''}.gif`
return ( return (
<div key={i} className={classNames('ship', size)} style={{ '--x': i + 3 } as CSSProperties}> <div key={i} className={classNames('ship', 's' + size)} style={{ '--x': i + 3 } as CSSProperties}>
<img src={filename} alt={filename} /> <img src={filename} alt={filename} />
</div> </div>
) )

View file

@ -2,10 +2,11 @@ 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'; import classNames from 'classnames';
import { TargetType } from '../interfaces';
function Target({ props: { preview, type, edges }, target: { x, y, show } }: { props: { preview?: boolean, type: string, edges: string[] }, target: { x: number, y: number, show: boolean } }) { function Target({ props: { preview, type, edges, imply }, target: { x, y, show } }: { props: { preview?: boolean, type: string, edges: string[], imply: boolean }, target: TargetType }) {
return ( return (
<div className={classNames('hit-svg', preview ? 'target-preview' : 'target', type, { show: show }, ...edges)} style={{ '--x': x, '--y': y } as CSSProperties}> <div className={classNames('hit-svg', preview ? 'target-preview' : 'target', type, { show: show }, ...edges, { imply: imply })} style={{ '--x': x, '--y': y } as CSSProperties}>
<FontAwesomeIcon icon={faCrosshairs} /> <FontAwesomeIcon icon={faCrosshairs} />
</div> </div>
) )

View file

@ -1,9 +1,17 @@
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, LastLeftTileType, TargetPreviewPosType, TargetPreviewType, TargetType } from '../interfaces'; import { HitType, ItemsType, LastLeftTileType, TargetListType, TargetModifierType, TargetPreviewPosType, TargetPreviewType, TargetType } from '../interfaces';
import Item from './Item'; import Item from './Item';
import Target from './Target'; import Target from './Target';
export const modes = {
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 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);
@ -13,36 +21,27 @@ function useGameEvent(count: number) {
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<keyof typeof modes>('none')
const [targetList, setTargetList] = useState<{ const [targetList, setTargetList] = useState<TargetListType[]>([])
show: boolean; const [targetPreviewList, setTargetPreviewList] = useState<TargetListType[]>([])
x: number;
y: number;
edges: string[];
}[]>([])
const [targetPreviewList, setTargetPreviewList] = useState<{
show: boolean;
x: number;
y: number;
edges: string[];
}[]>([])
const modes = useMemo(() => ({ function modXY<T>(e: TargetType, mod: TargetModifierType) {
none: { xEnable: true, yEnable: true, type: 'none' }, const { show, ...pos } = e
radar: { xEnable: true, yEnable: true, type: 'radar' }, const { target, params } = mod
hTorpedo: { xEnable: true, yEnable: false, type: 'torpedo' }, return {
vTorpedo: { xEnable: false, yEnable: true, type: 'torpedo' }, target: {
missle: { xEnable: false, yEnable: false, type: 'missle' } show,
}), []) x: pos.x + (target.x ?? 0),
y: pos.y + (target.y ?? 0)
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 } params
}
} }
const isSet = useCallback((x: number, y: number) => targetList.filter(target => x === target.x && y === target.y).length && target.show, [targetList, target]) 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 scopeGrid = useMemo(() => {
const { xEnable, yEnable, type } = modes[mode] const { xEnable, yEnable } = modes[mode]
const matrix: { x: number, y: number, edges: string[] }[][] = [] const matrix: TargetModifierType[][] = []
let y = 0 let y = 0
let x = 0 let x = 0
const yLength = (yEnable ? 2 : 0) const yLength = (yEnable ? 2 : 0)
@ -53,38 +52,47 @@ function useGameEvent(count: number) {
x = i2 + (xEnable ? -1 : 0); x = i2 + (xEnable ? -1 : 0);
(matrix[i] ??= [])[i2] = { (matrix[i] ??= [])[i2] = {
x, target: {
y, x,
edges: [ y,
i2 === 0 ? 'left' : '', },
i2 === xLength ? 'right' : '', params: {
i === 0 ? 'top' : '', edges: [
i === yLength ? 'bottom' : '', i2 === 0 ? 'left' : '',
] i2 === xLength ? 'right' : '',
i === 0 ? 'top' : '',
i === yLength ? 'bottom' : '',
],
imply: false
}
} }
} }
} }
const fields = matrix.reduce((prev, curr) => [...prev, ...curr], []) const fields = matrix.reduce((prev, curr) => [...prev, ...curr], [])
return { fields, type } return fields
}, [modes, mode]) }, [mode])
const Targets = useCallback((targets: { show: boolean, x: number, y: number, edges: string[] }[], preview?: boolean) => { const Targets = useCallback((targets: TargetListType[], preview?: boolean) => {
const { type } = scopeGrid const { type } = modes[mode]
return targets.map(({ edges, ...target }, i) => <Target key={i} props={{ type, preview, edges }} target={target} />) return targets.map(({ target, params }, i) => <Target key={i} props={{ type, preview, ...params }} target={target} />)
}, [scopeGrid, mode]) }, [mode])
useEffect(() => { useEffect(() => {
const { fields } = scopeGrid const result = scopeGrid.map(e => modXY(target, e))
const result = fields.map(e => modXY(target, e)) .filter(({ target: { x, y } }) => {
.filter(({ x, y }) => {
const border = [ const border = [
x < 2, x < 2,
x > count, x > count,
y < 2, y < 2,
y > count, y > count,
].reduce((prev, curr) => prev || curr, false) ].reduce((prev, curr) => prev || curr, false)
// console.log(!isHit(hits, x, y).length, !borders) return !border
return !isHit(hits, x, y).length && !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 => { setTargetList(e => {
if (JSON.stringify(e) === JSON.stringify(result)) if (JSON.stringify(e) === JSON.stringify(result))
@ -94,24 +102,30 @@ function useGameEvent(count: number) {
}, [scopeGrid, target, count, hits]); }, [scopeGrid, target, count, hits]);
useEffect(() => { useEffect(() => {
const { fields } = scopeGrid const result = scopeGrid.map(e => modXY(targetPreview, e))
const result = fields.map(e => modXY(targetPreview, e)) .filter(({ target: { x, y } }) => {
.filter(({ x, y }) => {
const border = [ const border = [
x < 2, x < 2,
x > count + 1, x > count + 1,
y < 2, y < 2,
y > count + 1, y > count + 1,
].reduce((prev, curr) => prev || curr, false) ].reduce((prev, curr) => prev || curr, false)
// console.log(!isHit(hits, x, y).length, !isSet(x, y), !borders) return !border
return !isHit(hits, x, y).length && !isSet(x, y) && !border }).map(field => {
const { target, params } = field
const { x, y } = target
if (isHit(hits, x, y).length || isSet(x, y))
return { ...field, params: { ...params, imply: true } }
return field
}) })
if (!targetPreviewPos.shouldShow)
return
setTargetPreviewList(e => { setTargetPreviewList(e => {
if (JSON.stringify(e) === JSON.stringify(result)) if (JSON.stringify(e) === JSON.stringify(result))
return e return e
return result return result
}) })
}, [scopeGrid, targetPreview, count, hits, isSet]); }, [scopeGrid, targetPreview, count, hits, isSet, targetPreviewPos.shouldShow]);
// handle visibility and position change of targetPreview // handle visibility and position change of targetPreview
useEffect(() => { useEffect(() => {
@ -162,13 +176,13 @@ function useGameEvent(count: number) {
</>, [Targets, targetList, targetPreviewList]) </>, [Targets, targetList, targetPreviewList])
const eventBar = useMemo(() => { const eventBar = useMemo(() => {
const items = [ const items: ItemsType[] = [
{ icon: 'burger-menu', text: 'Menu' }, { icon: 'burger-menu', text: 'Menu' },
{ icon: 'radar', text: 'Radar scan', type: 'radar' }, { icon: 'radar', text: 'Radar scan', type: 'radar', amount: 1 },
{ icon: 'missle', text: 'Fire torpedo', type: 'hTorpedo' }, { icon: 'missle', text: 'Fire torpedo', type: 'hTorpedo', amount: 1 },
{ icon: 'scope', text: 'Fire missle', type: 'missle' }, { icon: 'scope', text: 'Fire missle', type: 'missle' },
{ 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) => (

View file

@ -1,34 +1,78 @@
export interface LastLeftTileType { import { modes } from "./components/useGameEvent";
export type LastLeftTileType = {
x: number, x: number,
y: number y: number
} }
export interface TargetType { export type TargetType = {
show: boolean, show: boolean,
x: number, x: number,
y: number y: number
}; };
export interface TargetPreviewType { export type TargetPreviewType = {
show: boolean, show: boolean,
x: number, x: number,
y: number y: number
}; };
export interface TargetPreviewPosType { export type TargetPreviewPosType = {
shouldShow: boolean, shouldShow: boolean,
x: number, x: number,
y: number y: number
} }
export interface FieldType { export type TargetListType = {
target: {
show: boolean,
x: number,
y: number,
},
params: {
edges: string[],
imply: boolean
}
}
export type TargetModifierType = {
target: {
x: number,
y: number,
},
params: {
edges: string[],
imply: boolean
}
}
export type ItemsType = {
icon: string,
text: string,
type?: keyof typeof modes,
amount?: number,
}
export type FieldType = {
field: string, field: string,
x: number, x: number,
y: number, y: number,
}; };
export interface HitType { export type HitType = {
hit: boolean, hit: boolean,
x: number, x: number,
y: number, y: number,
}; };
interface fireMissle { type: 'fireMissle', payload: { x: number, y: number, hit: boolean } }; type fireMissle = {
interface removeMissle { type: 'removeMissle', payload: { x: number, y: number, hit: boolean } }; type: 'fireMissle',
payload: {
x: number,
y: number,
hit: boolean
}
};
type removeMissle = {
type:
'removeMissle',
payload: {
x: number,
y: number,
hit: boolean
}
};
export type HitDispatchType = fireMissle | removeMissle; export type HitDispatchType = fireMissle | removeMissle;

View file

@ -154,7 +154,7 @@ body {
color: red; color: red;
} }
} }
&.target { &.target {
--color: red; --color: red;
color: var(--color); color: var(--color);
@ -172,6 +172,10 @@ body {
opacity: 1; opacity: 1;
} }
&.imply>svg {
opacity: 0;
}
&.left { &.left {
border-left: 2px solid var(--color); border-left: 2px solid var(--color);
} }
@ -188,6 +192,22 @@ body {
border-bottom: 2px solid var(--color); border-bottom: 2px solid var(--color);
} }
&.imply.left {
border-left: 2px dashed white;
}
&.imply.right {
border-right: 2px dashed white;
}
&.imply.top {
border-top: 2px dashed white;
}
&.imply.bottom {
border-bottom: 2px dashed white;
}
&.left.top { &.left.top {
border-top-left-radius: 8px; border-top-left-radius: 8px;
} }
@ -246,12 +266,31 @@ body {
gap: .5rem; gap: .5rem;
width: 128px; width: 128px;
img { .container {
width: 64px; img {
padding: 8px; width: 64px;
@include pixelart; padding: 8px;
background-color: white; @include pixelart;
border-radius: 1rem; background-color: white;
border-radius: 1rem;
}
&.amount {
position: relative;
&::after {
content: var(--amount);
position: absolute;
top: -6px;
right: -6px;
color: black;
background-color: white;
border: 1px solid black;
border-radius: 8px;
font-size: 16px;
padding: 2px 8px;
}
}
} }
span { span {