diff --git a/leaky-ships/components/Item.tsx b/leaky-ships/components/Item.tsx index be562dd..9218365 100644 --- a/leaky-ships/components/Item.tsx +++ b/leaky-ships/components/Item.tsx @@ -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: { icon: string, text: string, + amount?: number, callback: () => void } }) { return (
- {`${icon}.png`} +
+ {`${icon}.png`} +
{text}
) diff --git a/leaky-ships/components/Ships.tsx b/leaky-ships/components/Ships.tsx index ac03ff2..a7af171 100644 --- a/leaky-ships/components/Ships.tsx +++ b/leaky-ships/components/Ships.tsx @@ -15,7 +15,7 @@ function Ships() { {shipIndexes.map(({ size, index }, i) => { const filename = `/assets/ship_blue_${size}x${index ? '_' + index : ''}.gif` return ( -
+
{filename}
) diff --git a/leaky-ships/components/Target.tsx b/leaky-ships/components/Target.tsx index 085ad8d..ce1f755 100644 --- a/leaky-ships/components/Target.tsx +++ b/leaky-ships/components/Target.tsx @@ -2,10 +2,11 @@ import { faCrosshairs } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { CSSProperties } from 'react'; 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 ( -
+
) diff --git a/leaky-ships/components/useGameEvent.tsx b/leaky-ships/components/useGameEvent.tsx index 2c05269..e247708 100644 --- a/leaky-ships/components/useGameEvent.tsx +++ b/leaky-ships/components/useGameEvent.tsx @@ -1,9 +1,17 @@ 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 { HitType, ItemsType, LastLeftTileType, TargetListType, TargetModifierType, TargetPreviewPosType, TargetPreviewType, TargetType } from '../interfaces'; import Item from './Item'; 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) { const [lastLeftTile, setLastLeftTile] = useState(initlialLastLeftTile); const [target, setTarget] = useState(initlialTarget); @@ -13,36 +21,27 @@ function useGameEvent(count: number) { 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 [targetList, setTargetList] = useState([]) + const [targetPreviewList, setTargetPreviewList] = useState([]) - 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 } + function modXY(e: TargetType, mod: TargetModifierType) { + const { show, ...pos } = e + const { target, params } = mod + return { + target: { + 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 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[] }[][] = [] + const { xEnable, yEnable } = modes[mode] + const matrix: TargetModifierType[][] = [] let y = 0 let x = 0 const yLength = (yEnable ? 2 : 0) @@ -53,38 +52,47 @@ function useGameEvent(count: number) { x = i2 + (xEnable ? -1 : 0); (matrix[i] ??= [])[i2] = { - x, - y, - edges: [ - i2 === 0 ? 'left' : '', - i2 === xLength ? 'right' : '', - i === 0 ? 'top' : '', - i === yLength ? 'bottom' : '', - ] + 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, type } - }, [modes, mode]) + return fields + }, [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]) + const Targets = useCallback((targets: TargetListType[], preview?: boolean) => { + const { type } = modes[mode] + return targets.map(({ target, params }, i) => ) + }, [mode]) useEffect(() => { - const { fields } = scopeGrid - const result = fields.map(e => modXY(target, e)) - .filter(({ x, y }) => { + 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) - // console.log(!isHit(hits, x, y).length, !borders) - return !isHit(hits, x, y).length && !border + 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)) @@ -94,24 +102,30 @@ function useGameEvent(count: number) { }, [scopeGrid, target, count, hits]); useEffect(() => { - const { fields } = scopeGrid - const result = fields.map(e => modXY(targetPreview, e)) - .filter(({ x, y }) => { + const result = scopeGrid.map(e => modXY(targetPreview, e)) + .filter(({ target: { 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 + return !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 => { if (JSON.stringify(e) === JSON.stringify(result)) return e return result }) - }, [scopeGrid, targetPreview, count, hits, isSet]); + }, [scopeGrid, targetPreview, count, hits, isSet, targetPreviewPos.shouldShow]); // handle visibility and position change of targetPreview useEffect(() => { @@ -162,13 +176,13 @@ function useGameEvent(count: number) { , [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' } - ] + const items: ItemsType[] = [ + { icon: 'burger-menu', text: 'Menu' }, + { icon: 'radar', text: 'Radar scan', type: 'radar', amount: 1 }, + { icon: 'missle', text: 'Fire torpedo', type: 'hTorpedo', amount: 1 }, + { icon: 'scope', text: 'Fire missle', type: 'missle' }, + { icon: 'gear', text: 'Settings' } + ] return (
{items.map((e, i) => ( diff --git a/leaky-ships/interfaces.ts b/leaky-ships/interfaces.ts index 3ad75f4..83bd8ec 100644 --- a/leaky-ships/interfaces.ts +++ b/leaky-ships/interfaces.ts @@ -1,34 +1,78 @@ -export interface LastLeftTileType { +import { modes } from "./components/useGameEvent"; + +export type LastLeftTileType = { x: number, y: number } -export interface TargetType { +export type TargetType = { show: boolean, x: number, y: number }; -export interface TargetPreviewType { +export type TargetPreviewType = { show: boolean, x: number, y: number }; -export interface TargetPreviewPosType { +export type TargetPreviewPosType = { shouldShow: boolean, x: 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, x: number, y: number, }; -export interface HitType { +export type HitType = { hit: boolean, x: number, y: number, }; -interface fireMissle { type: 'fireMissle', payload: { x: number, y: number, hit: boolean } }; -interface removeMissle { type: 'removeMissle', payload: { x: number, y: number, hit: boolean } }; +type fireMissle = { + 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; diff --git a/leaky-ships/styles/App.scss b/leaky-ships/styles/App.scss index 73b72ee..bea03e9 100644 --- a/leaky-ships/styles/App.scss +++ b/leaky-ships/styles/App.scss @@ -154,7 +154,7 @@ body { color: red; } } - + &.target { --color: red; color: var(--color); @@ -172,6 +172,10 @@ body { opacity: 1; } + &.imply>svg { + opacity: 0; + } + &.left { border-left: 2px solid var(--color); } @@ -188,6 +192,22 @@ body { 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 { border-top-left-radius: 8px; } @@ -246,12 +266,31 @@ body { gap: .5rem; width: 128px; - img { - width: 64px; - padding: 8px; - @include pixelart; - background-color: white; - border-radius: 1rem; + .container { + img { + width: 64px; + padding: 8px; + @include pixelart; + 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 {