diff --git a/leaky-ships/components/Gamefield/BorderTiles.tsx b/leaky-ships/components/Gamefield/BorderTiles.tsx index 9038175..34c9568 100644 --- a/leaky-ships/components/Gamefield/BorderTiles.tsx +++ b/leaky-ships/components/Gamefield/BorderTiles.tsx @@ -1,23 +1,22 @@ -import { Position, MouseCursor } from "../../interfaces/frontend" +import { MouseCursor } from "../../interfaces/frontend" +import { count } from "./Gamefield" import { borderCN, cornerCN, fieldIndex } from "@lib/utils/helpers" import { CSSProperties, Dispatch, SetStateAction } from "react" type TilesType = { key: number isGameTile: boolean - classNameString: string + className: string x: number y: number } function BorderTiles({ - props: { count, settingTarget, setMouseCursor, setLastLeftTile }, + props: { settingTarget, setMouseCursor }, }: { props: { - count: number settingTarget: (isGameTile: boolean, x: number, y: number) => void setMouseCursor: Dispatch> - setLastLeftTile: Dispatch> } }) { let tilesProperties: TilesType[] = [] @@ -31,10 +30,10 @@ function BorderTiles({ const classNames = ["border-tile"] if (borderType) classNames.push("edge", borderType) if (isGameTile) classNames.push("game-tile") - const classNameString = classNames.join(" ") + const className = classNames.join(" ") tilesProperties.push({ key, - classNameString, + className, isGameTile, x: x + 1, y: y + 1, @@ -44,17 +43,16 @@ function BorderTiles({ return ( <> - {tilesProperties.map(({ key, classNameString, isGameTile, x, y }) => { + {tilesProperties.map(({ key, className, isGameTile, x, y }) => { return (
settingTarget(isGameTile, x, y)} onMouseEnter={() => setMouseCursor({ x, y, shouldShow: isGameTile }) } - onMouseLeave={() => setLastLeftTile({ x, y })} >
) })} diff --git a/leaky-ships/components/Gamefield/Gamefield.tsx b/leaky-ships/components/Gamefield/Gamefield.tsx index 10ccee5..8173228 100644 --- a/leaky-ships/components/Gamefield/Gamefield.tsx +++ b/leaky-ships/components/Gamefield/Gamefield.tsx @@ -1,35 +1,98 @@ // import Bluetooth from './Bluetooth' // import FogImages from './FogImages' +import { Hit, MouseCursor, Target } from "../../interfaces/frontend" import Labeling from "./Labeling" import Ships from "./Ships" -import useGameEvent from "@hooks/useGameEvent" -import { CSSProperties } from "react" +import BorderTiles from "@components/Gamefield/BorderTiles" +import EventBar from "@components/Gamefield/EventBar" +import HitElems from "@components/Gamefield/HitElems" +import Targets from "@components/Gamefield/Targets" +import { + hitReducer, + initlialTarget, + initlialTargetPreview, + initlialMouseCursor, + overlapsWithAnyBorder, + isHit, + composeTargetTiles, +} from "@lib/utils/helpers" +import { CSSProperties, useCallback } from "react" +import { useEffect, useReducer, useState } from "react" + +export const count = 12 function Gamefield() { - const count = 12 - const { BorderTiles, HitElems, Targets, EventBar } = useGameEvent(count) + const [target, setTarget] = useState(initlialTarget) + const [targetPreview, setTargetPreview] = useState( + initlialTargetPreview + ) + const [mouseCursor, setMouseCursor] = + useState(initlialMouseCursor) + const [hits, DispatchHits] = useReducer(hitReducer, [] as Hit[]) + const [mode, setMode] = useState(0) + + const settingTarget = useCallback( + (isGameTile: boolean, x: number, y: number) => { + if (!isGameTile || isHit(x, y, hits).length) return + setTarget((t) => { + if (t.x === x && t.y === y && t.show) { + DispatchHits({ + type: "fireMissile", + payload: { hit: (x + y) % 2 !== 0, x, y }, + }) + return { preview: false, show: false, x, y } + } else { + const target = { show: true, x, y } + if (overlapsWithAnyBorder(target, mode)) return t + return target + } + }) + }, + [hits, mode] + ) + + useEffect(() => { + console.log(1) + const { x, y, show } = target + const { shouldShow, ...position } = mouseCursor + if (!shouldShow || overlapsWithAnyBorder(position, mode)) + setTargetPreview((e) => ({ ...e, show: false })) + else { + console.log(2, position) + setTargetPreview({ + ...position, + show: !show || x !== position.x || y !== position.y, + }) + } + }, [mode, mouseCursor, target]) return (
{/* */} -
+
+ setMouseCursor((e) => ({ ...e, shouldShow: false })) + } + > {/* Bordes */} - + {/* Collumn lettes and row numbers */} {/* Ships */} - - + {/* Fog images */} {/* */} - + + {/* Debug */}
- +
) } diff --git a/leaky-ships/components/Gamefield/GamefieldPointer.tsx b/leaky-ships/components/Gamefield/GamefieldPointer.tsx index 6957a17..0863826 100644 --- a/leaky-ships/components/Gamefield/GamefieldPointer.tsx +++ b/leaky-ships/components/Gamefield/GamefieldPointer.tsx @@ -10,9 +10,11 @@ export interface PointerProps extends Target, TargetList { } function GamefieldPointer({ - props: { preview, x, y, show, type, edges, imply }, + props: { x, y, show, type, edges, imply }, + preview, }: { props: PointerProps + preview?: boolean }) { const isRadar = type === "radar" const style = !(isRadar && !edges.filter((s) => s).length) diff --git a/leaky-ships/components/Gamefield/Targets.tsx b/leaky-ships/components/Gamefield/Targets.tsx index 9bc34a4..5f6a349 100644 --- a/leaky-ships/components/Gamefield/Targets.tsx +++ b/leaky-ships/components/Gamefield/Targets.tsx @@ -1,24 +1,26 @@ -import { Target } from "../../interfaces/frontend" -import GamefieldPointer, { PointerProps } from "./GamefieldPointer" +import { Hit, Target } from "../../interfaces/frontend" +import GamefieldPointer from "./GamefieldPointer" +import { composeTargetTiles } from "@lib/utils/helpers" import React from "react" function Targets({ - props: { composeTargetTiles, target, targetPreview }, + props: { target, targetPreview, mode, hits }, }: { props: { - composeTargetTiles: (target: Target) => PointerProps[] target: Target targetPreview: Target + mode: number + hits: Hit[] } }) { return ( <> {[ - ...composeTargetTiles(target).map((props, i) => ( + ...composeTargetTiles(target, mode, hits).map((props, i) => ( )), - ...composeTargetTiles(targetPreview).map((props, i) => ( - + ...composeTargetTiles(targetPreview, mode, hits).map((props, i) => ( + )), ]} diff --git a/leaky-ships/components/Lobby/LobbyFrame.tsx b/leaky-ships/components/Lobby/LobbyFrame.tsx index cfe7147..6aa8155 100644 --- a/leaky-ships/components/Lobby/LobbyFrame.tsx +++ b/leaky-ships/components/Lobby/LobbyFrame.tsx @@ -9,9 +9,26 @@ import { useSession } from "next-auth/react" import { useRouter } from "next/router" import { Fragment, useEffect, useState } from "react" +function WithDots({ children }: { children: string }) { + const [dots, setDots] = useState(3) + + useEffect(() => { + const interval = setInterval(() => setDots((e) => (e % 3) + 1), 1000) + return () => clearInterval(interval) + }, []) + return ( + <> + {children + " "} + {Array.from(Array(dots), () => ".").join("")} + {Array.from(Array(3 - dots), (_, i) => ( +   + ))} + + ) +} + function LobbyFrame({ openSettings }: { openSettings: () => void }) { const { payload, full, leave, reset } = useGameProps() - const [dots, setDots] = useState(3) const { isConnected } = useSocket() const router = useRouter() const { data: session } = useSession() @@ -21,12 +38,6 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) { socket.emit("update", full) }, [full, payload?.game?.id, isConnected]) - useEffect(() => { - if (payload?.player2) return - const interval = setInterval(() => setDots((e) => (e % 3) + 1), 1000) - return () => clearInterval(interval) - }, [payload?.player2]) - return (
@@ -58,11 +69,9 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) { /> ) : (

- Warte auf {isConnected ? "Spieler 2" : "Verbindung"}{" "} - {Array.from(Array(dots), () => ".").join("")} - {Array.from(Array(3 - dots), (_, i) => ( -   - ))} + + {"Warte auf " + (isConnected ? "Spieler 2" : "Verbindung")} +

)}
diff --git a/leaky-ships/components/Lobby/Player.tsx b/leaky-ships/components/Lobby/Player.tsx index 3e9da40..df6f2c0 100644 --- a/leaky-ships/components/Lobby/Player.tsx +++ b/leaky-ships/components/Lobby/Player.tsx @@ -14,7 +14,7 @@ function Player({ }) { const text = player?.name ?? "Spieler " + (player?.index === "player2" ? "2" : "1") - const primary = userId === player?.id + const primary = userId && userId === player?.id return (
diff --git a/leaky-ships/hooks/useGameEvent.tsx b/leaky-ships/hooks/useGameEvent.tsx deleted file mode 100644 index 6d0e46d..0000000 --- a/leaky-ships/hooks/useGameEvent.tsx +++ /dev/null @@ -1,235 +0,0 @@ -import { - Hit, - Mode, - MouseCursor, - Target, - Position, -} from "../interfaces/frontend" -import BorderTiles from "@components/Gamefield/BorderTiles" -import EventBar from "@components/Gamefield/EventBar" -import type { PointerProps } from "@components/Gamefield/GamefieldPointer" -import HitElems from "@components/Gamefield/HitElems" -import Targets from "@components/Gamefield/Targets" -import { - hitReducer, - initlialLastLeftTile, - initlialTarget, - initlialTargetPreview, - initlialMouseCursor, -} from "@lib/utils/helpers" -import { useCallback, useEffect, useReducer, useState } from "react" - -const modes: Mode[] = [ - { - pointerGrid: Array.from(Array(3), () => Array.from(Array(3))), - type: "radar", - }, - { - pointerGrid: Array.from(Array(3), () => Array.from(Array(1))), - type: "htorpedo", - }, - { - pointerGrid: Array.from(Array(1), () => Array.from(Array(3))), - type: "vtorpedo", - }, - { - pointerGrid: Array.from(Array(1), () => Array.from(Array(1))), - type: "missile", - }, -] - -function useGameEvent(count: number) { - const [lastLeftTile, setLastLeftTile] = - useState(initlialLastLeftTile) - const [target, setTarget] = useState(initlialTarget) - const [eventReady, setEventReady] = useState(false) - const [appearOK, setAppearOK] = useState(false) - const [targetPreview, setTargetPreview] = useState( - initlialTargetPreview - ) - const [mouseCursor, setMouseCursor] = - useState(initlialMouseCursor) - const [hits, DispatchHits] = useReducer(hitReducer, [] as Hit[]) - const [mode, setMode] = useState(0) - - const targetList = useCallback( - (target: Position) => { - const { pointerGrid, type } = modes[mode] - const xLength = pointerGrid.length - const yLength = pointerGrid[0].length - const { x: targetX, y: targetY } = target - return pointerGrid - .map((arr, i) => { - return arr.map((_, i2) => { - const relativeX = -Math.floor(xLength / 2) + i - const relativeY = -Math.floor(yLength / 2) + i2 - const x = targetX + (relativeX ?? 0) - const y = targetY + (relativeY ?? 0) - return { - x, - y, - type, - edges: [ - i === 0 ? "left" : "", - i === xLength - 1 ? "right" : "", - i2 === 0 ? "top" : "", - i2 === yLength - 1 ? "bottom" : "", - ], - } - }) - }) - .reduce((prev, curr) => [...prev, ...curr], []) - }, - [mode] - ) - - const isHit = useCallback( - (x: number, y: number) => { - return hits.filter((h) => h.x === x && h.y === y) - }, - [hits] - ) - - const settingTarget = useCallback( - (isGameTile: boolean, x: number, y: number) => { - if (!isGameTile || isHit(x, y).length) return - setMouseCursor((e) => ({ ...e, shouldShow: false })) - setTarget((t) => { - if (t.x === x && t.y === y && t.show) { - DispatchHits({ - type: "fireMissile", - payload: { hit: (x + y) % 2 !== 0, x, y }, - }) - return { preview: false, show: false, x, y } - } else { - const target = { preview: false, show: true, x, y } - const hasAnyBorder = targetList(target).filter(({ x, y }) => - isBorder(x, y, count) - ).length - if (hasAnyBorder) return t - return target - } - }) - }, - [count, isHit, targetList] - ) - - const isSet = useCallback( - (x: number, y: number) => { - return ( - !!targetList(target).filter((field) => x === field.x && y === field.y) - .length && target.show - ) - }, - [target, targetList] - ) - - const composeTargetTiles = useCallback( - (target: Target): PointerProps[] => { - const { preview, show } = target - const result = targetList(target).map(({ x, y, type, edges }) => { - return { - preview, - x, - y, - show, - type, - edges, - imply: !!isHit(x, y).length || (!!isSet(x, y) && preview), - } - }) - return result - }, - [isHit, isSet, targetList] - ) - - // handle visibility and position change of targetPreview - useEffect(() => { - 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 - const hasLeft = x === lastLeftTile.x && y === lastLeftTile.y - const isSet = x === target.x && y === target.y && target.show - - if (show && !appearOK) setTargetPreview((e) => ({ ...e, show: false })) - if ( - !show && - mouseCursor.shouldShow && - eventReady && - appearOK && - !isHit(x, y).length && - !isSet && - !hasLeft - ) - setTargetPreview((e) => ({ ...e, show: true })) - }, [ - targetPreview, - mouseCursor.shouldShow, - isHit, - eventReady, - appearOK, - lastLeftTile, - target, - ]) - - // enable targetPreview event again after 200 ms. - useEffect(() => { - setEventReady(false) - const previewTarget = { x: mouseCursor.x, y: mouseCursor.y } - const hasAnyBorder = targetList(previewTarget).filter(({ x, y }) => - isBorder(x, y, count) - ).length - if (targetPreview.show || !appearOK || hasAnyBorder) return - const autoTimeout = setTimeout(() => { - setTargetPreview((e) => ({ ...e, ...previewTarget })) - setEventReady(true) - setAppearOK(true) - }, 300) - - // or abort if state has changed early - return () => { - clearTimeout(autoTimeout) - } - }, [ - appearOK, - count, - mouseCursor.x, - mouseCursor.y, - targetList, - targetPreview.show, - ]) - - // approve targetPreview new position after 200 mil. sec. - useEffect(() => { - // early return to start cooldown only when about to show up - const autoTimeout = setTimeout( - () => { - setAppearOK(!targetPreview.show) - }, - targetPreview.show ? 500 : 300 - ) - - // or abort if movement is repeated early - return () => { - clearTimeout(autoTimeout) - } - }, [targetPreview.show]) - - return { - BorderTiles: () => ( - - ), - HitElems: () => , - Targets: () => ( - - ), - EventBar: () => , - } -} - -function isBorder(x: number, y: number, count: number) { - return x < 2 || x > count + 1 || y < 2 || y > count + 1 -} - -export default useGameEvent diff --git a/leaky-ships/interfaces/frontend.ts b/leaky-ships/interfaces/frontend.ts index 9d0bb9a..bc2470f 100644 --- a/leaky-ships/interfaces/frontend.ts +++ b/leaky-ships/interfaces/frontend.ts @@ -3,7 +3,6 @@ export interface Position { y: number } export interface Target extends Position { - preview: boolean show: boolean } export interface MouseCursor extends Position { diff --git a/leaky-ships/lib/utils/helpers.ts b/leaky-ships/lib/utils/helpers.ts index 7e907e2..2aa6b9b 100644 --- a/leaky-ships/lib/utils/helpers.ts +++ b/leaky-ships/lib/utils/helpers.ts @@ -1,4 +1,13 @@ -import type { Hit, HitDispatch } from "../../interfaces/frontend" +import type { + Hit, + HitDispatch, + Mode, + Position, + Target, + TargetList, +} from "../../interfaces/frontend" +import { count } from "@components/Gamefield/Gamefield" +import { PointerProps } from "@components/Gamefield/GamefieldPointer" export function borderCN(count: number, x: number, y: number) { if (x === 0) return "left" @@ -28,10 +37,92 @@ export function hitReducer(formObject: Hit[], action: HitDispatch) { return formObject } } -export const initlialLastLeftTile = { - x: 0, - y: 0, + +const modes: Mode[] = [ + { + pointerGrid: Array.from(Array(3), () => Array.from(Array(3))), + type: "radar", + }, + { + pointerGrid: Array.from(Array(3), () => Array.from(Array(1))), + type: "htorpedo", + }, + { + pointerGrid: Array.from(Array(1), () => Array.from(Array(3))), + type: "vtorpedo", + }, + { + pointerGrid: Array.from(Array(1), () => Array.from(Array(1))), + type: "missile", + }, +] + +function isBorder(x: number, y: number, count: number) { + return x < 2 || x > count + 1 || y < 2 || y > count + 1 } + +export function isHit(x: number, y: number, hits: Hit[]) { + return hits.filter((h) => h.x === x && h.y === y) +} + +function isSet(x: number, y: number, targetList: TargetList[], show: boolean) { + return ( + !!targetList.filter((field) => x === field.x && y === field.y).length && + show + ) +} + +function targetList( + { x: targetX, y: targetY }: Position, + mode: number +): TargetList[] { + const { pointerGrid, type } = modes[mode] + const xLength = pointerGrid.length + const yLength = pointerGrid[0].length + return pointerGrid + .map((arr, i) => { + return arr.map((_, i2) => { + const relativeX = -Math.floor(xLength / 2) + i + const relativeY = -Math.floor(yLength / 2) + i2 + const x = targetX + (relativeX ?? 0) + const y = targetY + (relativeY ?? 0) + return { + x, + y, + type, + edges: [ + i === 0 ? "left" : "", + i === xLength - 1 ? "right" : "", + i2 === 0 ? "top" : "", + i2 === yLength - 1 ? "bottom" : "", + ], + } + }) + }) + .reduce((prev, curr) => [...prev, ...curr], []) +} + +export function overlapsWithAnyBorder(target: Position, mode: number) { + return targetList(target, mode).filter(({ x, y }) => isBorder(x, y, count)) + .length +} + +export function composeTargetTiles( + target: Target, + mode: number, + hits: Hit[] +): PointerProps[] { + const { show } = target + return targetList(target, mode).map((targetItem) => { + const { x, y } = targetItem + return { + ...targetItem, + show, + imply: !!isHit(x, y, hits).length, + } + }) +} + export const initlialTarget = { preview: false, show: false, diff --git a/leaky-ships/pages/api/game/create.ts b/leaky-ships/pages/api/game/create.ts index f4d9e61..8c65d61 100644 --- a/leaky-ships/pages/api/game/create.ts +++ b/leaky-ships/pages/api/game/create.ts @@ -43,6 +43,11 @@ export default async function create( create: { userId: id, index: "player1", + chats: { + create: { + event: "created", + }, + }, }, }, }, diff --git a/leaky-ships/pages/api/ws.ts b/leaky-ships/pages/api/ws.ts index 946fa99..f3bbc56 100644 --- a/leaky-ships/pages/api/ws.ts +++ b/leaky-ships/pages/api/ws.ts @@ -119,7 +119,6 @@ const SocketHandler = async ( }) let body: GamePropsSchema if (user_Game.index === "player1" && enemy) { - console.log(1) body = composeBody( ( await prisma.user_Game.update({ @@ -140,7 +139,6 @@ const SocketHandler = async ( ).game ) } else { - console.log(2) const game = await prisma.game.findUnique({ where: { id: socket.data.gameId, @@ -151,7 +149,6 @@ const SocketHandler = async ( body = composeBody(game) } const { payload, hash } = body - console.log(payload?.player1, payload?.player2) if (!payload || !hash) return cb(false) io.to(socket.data.gameId).emit( "playerEvent", diff --git a/leaky-ships/pages/lobby.tsx b/leaky-ships/pages/lobby.tsx index 1cdab20..53f32ca 100644 --- a/leaky-ships/pages/lobby.tsx +++ b/leaky-ships/pages/lobby.tsx @@ -16,6 +16,12 @@ export default function Lobby() { +
{ - const gamePromise = fetch("/api/game/" + (!pin ? "create" : "join"), { - method: "POST", - body: JSON.stringify({ pin }), - }) + const gameRequestPromise = fetch( + "/api/game/" + (!pin ? "create" : "join"), + { + method: "POST", + body: JSON.stringify({ pin }), + } + ) .then(isAuthenticated) .then((game) => GamePropsSchema.parse(game)) @@ -76,9 +82,9 @@ export default function Start() { hideProgressBar: true, closeButton: false, }) - const res = await gamePromise.catch(() => + const res = await gameRequestPromise.catch(() => toast.update(toastId, { - render: "Es ist ein Fehler aufgetreten 🤯", + render: "Es ist ein Fehler aufgetreten bei der Anfrage 🤯", type: "error", icon: Icons.error, theme: "colored", @@ -108,7 +114,7 @@ export default function Start() { ) .catch(() => toast.update(toastId, { - render: "Es ist ein Fehler aufgetreten 🤯", + render: "Es ist ein Fehler aufgetreten beim Seiten wechsel 🤯", type: "error", icon: Icons.error, theme: "colored", diff --git a/leaky-ships/styles/App.scss b/leaky-ships/styles/App.scss index 5241e6f..87fc692 100644 --- a/leaky-ships/styles/App.scss +++ b/leaky-ships/styles/App.scss @@ -197,7 +197,7 @@ body { } &.preview { - --color: lawngreen; + --color: forestgreen; background-color: #0001; // border: 5px dashed var(--color); } diff --git a/leaky-ships/styles/mixins/CP_Font.scss b/leaky-ships/styles/mixins/CP_Font.scss deleted file mode 100644 index cefb5a9..0000000 --- a/leaky-ships/styles/mixins/CP_Font.scss +++ /dev/null @@ -1,8 +0,0 @@ -@font-face { - font-family: "CP_Font"; - src: url("/fonts/cpfont_ote/CP Font.otf") format("opentype"); -} - -@mixin CP_Font { - font-family: "CP_Font", sans-serif; -}