commit
6de1784f16
6 changed files with 181 additions and 79 deletions
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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) => (
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue