diff --git a/leaky-ships/components/Gamefield/EventBar.tsx b/leaky-ships/components/Gamefield/EventBar.tsx index 45c630a..ce10ab2 100644 --- a/leaky-ships/components/Gamefield/EventBar.tsx +++ b/leaky-ships/components/Gamefield/EventBar.tsx @@ -1,4 +1,4 @@ -import { EventBarModes, Target } from "../../interfaces/frontend" +import { EventBarModes } from "../../interfaces/frontend" import Item from "./Item" import { GameSettings } from "@components/Lobby/SettingsFrame/Setting" import { @@ -17,6 +17,7 @@ import { import { useDrawProps } from "@hooks/useDrawProps" import { useGameProps } from "@hooks/useGameProps" import { socket } from "@lib/socket" +import { GamePropsSchema } from "@lib/zodSchemas" import { Dispatch, SetStateAction, @@ -26,30 +27,38 @@ import { useState, } from "react" +export function setGameSetting( + payload: GameSettings, + setSetting: (settings: GameSettings) => string | null, + full: (payload: GamePropsSchema) => void +) { + return () => { + const hash = setSetting(payload) + socket.emit("gameSetting", payload, (newHash) => { + if (newHash === hash) return + console.log("hash", hash, newHash) + socket.emit("update", full) + }) + } +} + function EventBar({ - props: { setMode, setTarget, clear }, + props: { setMode, clear }, }: { props: { setMode: Dispatch> - setTarget: Dispatch> clear: () => void } }) { const [menu, setMenu] = useState("main") const { shouldHide, color } = useDrawProps() - const { payload, setSetting, full } = useGameProps() + const { payload, setSetting, full, setTarget } = useGameProps() const gameSetting = useCallback( - (payload: GameSettings) => { - const hash = setSetting(payload) - socket.emit("gameSetting", payload, (newHash) => { - if (newHash === hash) return - console.log("hash", hash, newHash) - socket.emit("update", full) - }) - }, + (payload: GameSettings) => setGameSetting(payload, setSetting, full), [full, setSetting] ) + const items = useMemo( () => ({ main: [ @@ -168,33 +177,31 @@ function EventBar({ icon: faGlasses, text: "Spectators", disabled: !payload?.game?.allowSpectators, - callback: () => { - gameSetting({ allowSpectators: !payload?.game?.allowSpectators }) - }, + callback: gameSetting({ + allowSpectators: !payload?.game?.allowSpectators, + }), }, { icon: faSparkles, text: "Specials", disabled: !payload?.game?.allowSpecials, - callback: () => { - gameSetting({ allowSpecials: !payload?.game?.allowSpecials }) - }, + callback: gameSetting({ + allowSpecials: !payload?.game?.allowSpecials, + }), }, { icon: faComments, text: "Chat", disabled: !payload?.game?.allowChat, - callback: () => { - gameSetting({ allowChat: !payload?.game?.allowChat }) - }, + callback: gameSetting({ allowChat: !payload?.game?.allowChat }), }, { icon: faScribble, text: "Mark/Draw", disabled: !payload?.game?.allowMarkDraw, - callback: () => { - gameSetting({ allowMarkDraw: !payload?.game?.allowMarkDraw }) - }, + callback: gameSetting({ + allowMarkDraw: !payload?.game?.allowMarkDraw, + }), }, ], }), diff --git a/leaky-ships/components/Gamefield/Gamefield.tsx b/leaky-ships/components/Gamefield/Gamefield.tsx index 754f9aa..cc93e87 100644 --- a/leaky-ships/components/Gamefield/Gamefield.tsx +++ b/leaky-ships/components/Gamefield/Gamefield.tsx @@ -1,4 +1,4 @@ -import { Hit, MouseCursor, Target } from "../../interfaces/frontend" +import { MouseCursor } from "../../interfaces/frontend" // import Bluetooth from "./Bluetooth" // import FogImages from "./FogImages" import Labeling from "./Labeling" @@ -9,28 +9,29 @@ import HitElems from "@components/Gamefield/HitElems" import Targets from "@components/Gamefield/Targets" import { useDraw } from "@hooks/useDraw" import { useDrawProps } from "@hooks/useDrawProps" +import { useGameProps } from "@hooks/useGameProps" import { - hitReducer, - initlialTarget, - initlialTargetPreview, initlialMouseCursor, overlapsWithAnyBorder, isAlreadyHit, targetList, } from "@lib/utils/helpers" import { CSSProperties, useCallback } from "react" -import { useEffect, useReducer, useState } from "react" +import { useEffect, useState } from "react" export const count = 12 function Gamefield() { - const [target, setTarget] = useState(initlialTarget) - const [targetPreview, setTargetPreview] = useState( - initlialTargetPreview - ) const [mouseCursor, setMouseCursor] = useState(initlialMouseCursor) - const [hits, DispatchHits] = useReducer(hitReducer, [] as Hit[]) + const { + hits, + target, + targetPreview, + DispatchHits, + setTarget, + setTargetPreview, + } = useGameProps() const [mode, setMode] = useState(0) const settingTarget = useCallback( @@ -54,7 +55,7 @@ function Gamefield() { } else if (!overlapsWithAnyBorder(targetPreview, mode)) setTarget({ show: true, x, y }) }, - [hits, mode, target, targetPreview] + [DispatchHits, hits, mode, setTarget, target, targetPreview] ) useEffect(() => { @@ -68,7 +69,7 @@ function Gamefield() { show: !show || x !== position.x || y !== position.y, }) } - }, [mode, mouseCursor, target]) + }, [mode, mouseCursor, setTargetPreview, target]) const { canvasRef, onMouseDown, clear } = useDraw() const { enable, color, shouldHide } = useDrawProps() @@ -113,7 +114,7 @@ function Gamefield() { height="648" /> - + ) } diff --git a/leaky-ships/components/Gamefield/Ships.tsx b/leaky-ships/components/Gamefield/Ships.tsx index 016ca5c..183e043 100644 --- a/leaky-ships/components/Gamefield/Ships.tsx +++ b/leaky-ships/components/Gamefield/Ships.tsx @@ -1,29 +1,28 @@ +import { Ship } from "../../interfaces/frontend" import classNames from "classnames" import { CSSProperties } from "react" function Ships() { - let shipIndexes = [ - { size: 2, index: null }, - { size: 3, index: 1 }, - { size: 3, index: 2 }, - { size: 3, index: 3 }, - { size: 4, index: 1 }, - { size: 4, index: 2 }, + const shipIndexes: Ship[] = [ + { size: 2, variant: 1, x: 3, y: 3 }, + { size: 3, variant: 1, x: 4, y: 3 }, + { size: 3, variant: 2, x: 5, y: 3 }, + { size: 3, variant: 3, x: 6, y: 3 }, + { size: 4, variant: 1, x: 7, y: 3 }, + { size: 4, variant: 2, x: 8, y: 3 }, ] return ( <> - {shipIndexes.map(({ size, index }, i) => { - const filename = `/assets/ship_blue_${size}x${ - index ? "_" + index : "" - }.gif` + {shipIndexes.map(({ size, variant, x, y }, i) => { + const filename = `ship_blue_${size}x_${variant}.gif` return (
- {filename} + {filename}
) })} diff --git a/leaky-ships/components/Lobby/SettingsFrame/Setting.tsx b/leaky-ships/components/Lobby/SettingsFrame/Setting.tsx index d3504af..fcded48 100644 --- a/leaky-ships/components/Lobby/SettingsFrame/Setting.tsx +++ b/leaky-ships/components/Lobby/SettingsFrame/Setting.tsx @@ -1,3 +1,4 @@ +import { setGameSetting } from "@components/Gamefield/EventBar" import { faToggleLargeOff, faToggleLargeOn, @@ -17,12 +18,12 @@ export type GameSettings = { [key in GameSettingKeys]?: boolean } function Setting({ children, - props: { prop, gameSetting }, + prop, }: { children: ReactNode - props: { prop: GameSettingKeys; gameSetting: (payload: GameSettings) => void } + prop: GameSettingKeys }) { - const { payload } = useGameProps() + const { payload, setSetting, full } = useGameProps() const state = useMemo(() => payload?.game?.[prop], [payload?.game, prop]) return ( @@ -46,12 +47,15 @@ function Setting({ checked={state} type="checkbox" id={prop} - onChange={() => { - const payload = { - [prop]: !state, - } - gameSetting(payload) - }} + onChange={() => + setGameSetting( + { + [prop]: !state, + }, + setSetting, + full + ) + } hidden={true} /> diff --git a/leaky-ships/components/Lobby/SettingsFrame/Settings.tsx b/leaky-ships/components/Lobby/SettingsFrame/Settings.tsx index 309b277..c967739 100644 --- a/leaky-ships/components/Lobby/SettingsFrame/Settings.tsx +++ b/leaky-ships/components/Lobby/SettingsFrame/Settings.tsx @@ -44,15 +44,14 @@ function Settings({ closeSettings }: { closeSettings: () => void }) {
- - Erlaube Zuschauer - - - Erlaube spezial Items - - - Erlaube den Chat - - - Erlaube zeichen/makieren - + Erlaube Zuschauer + Erlaube spezial Items + Erlaube den Chat + Erlaube zeichen/makieren
diff --git a/leaky-ships/hooks/useDraw.ts b/leaky-ships/hooks/useDraw.ts index d8d8ce6..5c5b978 100644 --- a/leaky-ships/hooks/useDraw.ts +++ b/leaky-ships/hooks/useDraw.ts @@ -1,8 +1,9 @@ -import { Draw, Point } from "../interfaces/frontend" +import { Draw, DrawLineProps, Point } from "../interfaces/frontend" import { useDrawProps } from "./useDrawProps" +import { socket } from "@lib/socket" import { useEffect, useRef, useState } from "react" -function onDraw({ prevPoint, currentPoint, ctx, color }: Draw) { +function drawLine({ prevPoint, currentPoint, ctx, color }: Draw) { const { x: currX, y: currY } = currentPoint const lineColor = color const lineWidth = 5 @@ -52,7 +53,7 @@ export const useDraw = () => { const ctx = canvasRef.current?.getContext("2d") if (!ctx || !currentPoint) return - onDraw({ ctx, currentPoint, prevPoint: prevPoint.current, color }) + drawLine({ ctx, currentPoint, prevPoint: prevPoint.current, color }) prevPoint.current = currentPoint } @@ -80,5 +81,39 @@ export const useDraw = () => { } }, [color, mouseDown]) + useEffect(() => { + const canvas = canvasRef.current + if (!canvas) return + + const ctx = canvas.getContext("2d") + if (!ctx) return + + socket.on("playerEvent", (event) => { + if (!canvasRef.current?.toDataURL() || event.type !== "connect") return + console.log("sending canvas state") + socket.emit("canvas-state", canvasRef.current.toDataURL()) + }) + + socket.on("canvas-state-from-server", (state: string, index) => { + console.log("I received the state") + const img = new Image() + img.src = state + img.onload = () => { + ctx?.drawImage(img, 0, 0) + } + }) + + socket.on("draw-line", ({ prevPoint, currentPoint, color }, index) => { + if (!ctx) return console.log("no ctx here") + drawLine({ prevPoint, currentPoint, ctx, color }) + }) + + socket.on("clear", clear) + + return () => { + socket.removeAllListeners() + } + }) + return { canvasRef, onMouseDown, clear } } diff --git a/leaky-ships/hooks/useGameProps.ts b/leaky-ships/hooks/useGameProps.ts index a778368..cf6d7f2 100644 --- a/leaky-ships/hooks/useGameProps.ts +++ b/leaky-ships/hooks/useGameProps.ts @@ -1,12 +1,15 @@ +import { Hit, HitDispatch, Target } from "../interfaces/frontend" import { GameSettings } from "@components/Lobby/SettingsFrame/Setting" import { getPayloadwithChecksum } from "@lib/getPayloadwithChecksum" import { socket } from "@lib/socket" +import { initlialTarget, initlialTargetPreview } from "@lib/utils/helpers" import { GamePropsSchema, optionalGamePropsSchema, PlayerSchema, } from "@lib/zodSchemas" import { produce } from "immer" +import { SetStateAction } from "react" import { toast } from "react-toastify" import { create } from "zustand" import { devtools } from "zustand/middleware" @@ -16,9 +19,15 @@ const initialState: optionalGamePropsSchema & { isReady: boolean isConnected: boolean }[] + hits: Hit[] + target: Target + targetPreview: Target } = { payload: null, hash: null, + hits: [], + target: initlialTarget, + targetPreview: initlialTargetPreview, userStates: Array.from(Array(2), () => ({ isReady: false, isConnected: false, @@ -28,8 +37,11 @@ const initialState: optionalGamePropsSchema & { export type State = typeof initialState export type Action = { - setSetting: (settings: GameSettings) => string | null + DispatchHits: (action: HitDispatch) => void + setTarget: (target: SetStateAction) => void + setTargetPreview: (targetPreview: SetStateAction) => void setPlayer: (payload: { users: PlayerSchema[] }) => string | null + setSetting: (settings: GameSettings) => string | null full: (newProps: GamePropsSchema) => void leave: (cb: () => void) => void setIsReady: (payload: { i: number; isReady: boolean }) => void @@ -41,6 +53,34 @@ export const useGameProps = create()( devtools( (set) => ({ ...initialState, + DispatchHits: (action) => + set( + produce((state: State) => { + switch (action.type) { + case "fireMissile": + case "htorpedo": + case "vtorpedo": { + state.hits.push(...action.payload) + } + } + }) + ), + setTarget: (target) => + set( + produce((state: State) => { + if (typeof target === "function") + state.target = target(state.target) + else state.target = target + }) + ), + setTargetPreview: (targetPreview) => + set( + produce((state: State) => { + if (typeof targetPreview === "function") + state.targetPreview = targetPreview(state.target) + else state.targetPreview = targetPreview + }) + ), setPlayer: (payload) => { let hash: string | null = null set( diff --git a/leaky-ships/hooks/useSocket.ts b/leaky-ships/hooks/useSocket.ts index 8e05676..d5e2f3e 100644 --- a/leaky-ships/hooks/useSocket.ts +++ b/leaky-ships/hooks/useSocket.ts @@ -17,7 +17,6 @@ function useSocket() { full, setIsReady, setIsConnected, - hash: stateHash, } = useGameProps() const { data: session } = useSession() const router = useRouter() @@ -39,7 +38,6 @@ function useSocket() { useEffect(() => { if (!session?.user.id) return - socket.connect() socket.on("connect", () => { console.log("connected") @@ -115,9 +113,7 @@ function useSocket() { }) }) - socket.on("isReady", (payload) => { - setIsReady(payload) - }) + socket.on("isReady", setIsReady) socket.on("disconnect", () => { console.log("disconnect") diff --git a/leaky-ships/interfaces/NextApiSocket.ts b/leaky-ships/interfaces/NextApiSocket.ts index 9fad6c9..269d545 100644 --- a/leaky-ships/interfaces/NextApiSocket.ts +++ b/leaky-ships/interfaces/NextApiSocket.ts @@ -1,3 +1,4 @@ +import { DrawLineProps } from "./frontend" import { GameSettings } from "@components/Lobby/SettingsFrame/Setting" import { GamePropsSchema, PlayerSchema } from "@lib/zodSchemas" import type { Server as HTTPServer } from "http" @@ -43,6 +44,10 @@ export interface ServerToClientEvents { ) => void isReady: (payload: { i: number; isReady: boolean }) => void isConnected: (payload: { i: number; isConnected: boolean }) => void + "get-canvas-state": () => void + "canvas-state-from-server": (state: string, userIndex: number) => void + "draw-line": (props: DrawLineProps, userIndex: number) => void + clear: () => void } export interface ClientToServerEvents { @@ -53,6 +58,9 @@ export interface ClientToServerEvents { join: (withAck: (ack: boolean) => void) => void gameSetting: (payload: GameSettings, callback: (hash: string) => void) => void leave: (withAck: (ack: boolean) => void) => void + "canvas-state": (state: string) => void + "draw-line": (props: DrawLineProps) => void + clear: () => void } interface InterServerEvents { diff --git a/leaky-ships/interfaces/frontend.ts b/leaky-ships/interfaces/frontend.ts index d3f4be8..bd5e704 100644 --- a/leaky-ships/interfaces/frontend.ts +++ b/leaky-ships/interfaces/frontend.ts @@ -64,9 +64,17 @@ export interface Point { y: number } -export interface Draw { - ctx: CanvasRenderingContext2D +export interface DrawLineProps { currentPoint: Point prevPoint: Point | null color: string } + +export interface Draw extends DrawLineProps { + ctx: CanvasRenderingContext2D +} + +export interface Ship extends Position { + size: number + variant: number +} diff --git a/leaky-ships/lib/socket.ts b/leaky-ships/lib/socket.ts index cd10517..bf08521 100644 --- a/leaky-ships/lib/socket.ts +++ b/leaky-ships/lib/socket.ts @@ -3,5 +3,4 @@ import { io } from "socket.io-client" export const socket: cSocket = io({ path: "/api/ws", - autoConnect: false, }) diff --git a/leaky-ships/lib/utils/helpers.ts b/leaky-ships/lib/utils/helpers.ts index 7dd5308..93be435 100644 --- a/leaky-ships/lib/utils/helpers.ts +++ b/leaky-ships/lib/utils/helpers.ts @@ -26,19 +26,6 @@ export function cornerCN(count: number, x: number, y: number) { export function fieldIndex(count: number, x: number, y: number) { return y * (count + 2) + x } -export function hitReducer(formObject: Hit[], action: HitDispatch) { - switch (action.type) { - case "fireMissile": - case "htorpedo": - case "vtorpedo": { - const result = [...formObject, ...action.payload] - return result - } - - default: - return formObject - } -} const modes: Mode[] = [ { diff --git a/leaky-ships/pages/api/ws.ts b/leaky-ships/pages/api/ws.ts index b9b622a..a8a6b5c 100644 --- a/leaky-ships/pages/api/ws.ts +++ b/leaky-ships/pages/api/ws.ts @@ -2,6 +2,7 @@ import { NextApiResponseWithSocket, sServer, } from "../../interfaces/NextApiSocket" +import { DrawLineProps } from "../../interfaces/frontend" import { composeBody, gameSelects, @@ -183,6 +184,33 @@ const SocketHandler = async ( .emit("isConnected", { i: socket.data.index, isConnected: true }) }) + socket.on("canvas-state", (state) => { + if (!socket.data.gameId || !socket.data.index) return + console.log("received canvas state") + socket + .to(socket.data.gameId) + .emit("canvas-state-from-server", state, socket.data.index) + }) + + socket.on( + "draw-line", + ({ prevPoint, currentPoint, color }: DrawLineProps) => { + if (!socket.data.gameId || !socket.data.index) return + socket + .to(socket.data.gameId) + .emit( + "draw-line", + { prevPoint, currentPoint, color }, + socket.data.index + ) + } + ) + + socket.on("clear", () => { + if (!socket.data.gameId) return + socket.to(socket.data.gameId).emit("clear") + }) + socket.on("disconnecting", async () => { logging( "Disconnecting: " + JSON.stringify(Array.from(socket.rooms)), diff --git a/leaky-ships/public/assets/ship_blue_2x.gif b/leaky-ships/public/assets/ship_blue_2x_1.gif similarity index 100% rename from leaky-ships/public/assets/ship_blue_2x.gif rename to leaky-ships/public/assets/ship_blue_2x_1.gif diff --git a/leaky-ships/public/assets/ship_red_2x.gif b/leaky-ships/public/assets/ship_red_2x_1.gif similarity index 100% rename from leaky-ships/public/assets/ship_red_2x.gif rename to leaky-ships/public/assets/ship_red_2x_1.gif diff --git a/leaky-ships/styles/App.scss b/leaky-ships/styles/App.scss index 5d761d3..ea4df20 100644 --- a/leaky-ships/styles/App.scss +++ b/leaky-ships/styles/App.scss @@ -120,15 +120,15 @@ body { } &.s2 { - grid-column: 3 / 5; + grid-column: var(--y) / 5; } &.s3 { - grid-column: 3 / 6; + grid-column: var(--y) / 6; } &.s4 { - grid-column: 3 / 7; + grid-column: var(--y) / 7; } }