diff --git a/leaky-ships/src/components/Gamefield/EventBar.tsx b/leaky-ships/src/components/Gamefield/EventBar.tsx index e9436b5..ab0c7fb 100644 --- a/leaky-ships/src/components/Gamefield/EventBar.tsx +++ b/leaky-ships/src/components/Gamefield/EventBar.tsx @@ -26,6 +26,7 @@ import { modes } from "~/lib/utils/helpers" // import { Icons, toast } from "react-toastify" import { For, Show, createEffect } from "solid-js" import { useNavigate } from "solid-start" +import { clearDrawing } from "~/hooks/useDraw" import { color, setEnable, @@ -47,7 +48,7 @@ import { useSession } from "~/hooks/useSession" import { EventBarModes } from "../../interfaces/frontend" import Item from "./Item" -function EventBar(props: { clear: () => void }) { +function EventBar() { const { selfIndex, selfIsActiveIndex, selfUser, ships } = useSession() const navigator = useNavigate() @@ -60,21 +61,24 @@ function EventBar(props: { clear: () => void }) { setGameProps("menu", "menu") }, }, - gameProps.gameState === "running" - ? { - icon: faSwords, - text: "Attack", - callback: () => { - setGameProps("menu", "moves") - }, - } - : { - icon: faShip, - text: "Ships", - callback: () => { - setGameProps("menu", "moves") - }, - }, + { + icon: faSwords, + text: "Attack", + showWhen: () => + gameProps.gameState === "running" && + (selfIsActiveIndex() || gameProps.menu !== "main"), + callback: () => { + setGameProps("menu", "moves") + }, + }, + { + icon: faShip, + text: "Ships", + showWhen: () => gameProps.gameState !== "running", + callback: () => { + setGameProps("menu", "moves") + }, + }, { icon: "pen", text: "Draw", @@ -180,8 +184,18 @@ function EventBar(props: { clear: () => void }) { }, ], draw: [ - { icon: faBroomWide, text: "Clear", callback: props.clear }, - { icon: faPalette, text: "Color", iconColor: color() }, + { + icon: faBroomWide, + text: "Clear", + showWhen: selfIsActiveIndex, + callback: () => clearDrawing(selfIndex()), + }, + { + icon: faPalette, + text: "Color", + showWhen: selfIsActiveIndex, + iconColor: color(), + }, { icon: shouldHide() ? faEye : faEyeSlash, text: shouldHide() ? "Show" : "Hide", @@ -301,10 +315,8 @@ function EventBar(props: { clear: () => void }) { /> - {(e, i) => ( - + {(e) => ( + )} diff --git a/leaky-ships/src/components/Gamefield/Gamefield.tsx b/leaky-ships/src/components/Gamefield/Gamefield.tsx index 4557023..91a94ee 100644 --- a/leaky-ships/src/components/Gamefield/Gamefield.tsx +++ b/leaky-ships/src/components/Gamefield/Gamefield.tsx @@ -7,7 +7,8 @@ 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 { DrawingCanvas, clearDrawing } from "~/hooks/useDraw" +import { DrawingCanvas } from "~/hooks/useDraw" +import { setFrameSize } from "~/hooks/useDrawProps" import { full, gameProps, @@ -29,6 +30,7 @@ import Ships from "./Ships" export const count = 12 function Gamefield() { + let frameRef: HTMLDivElement const { ships } = useSession() const navigator = useNavigate() const { isConnected } = useSocket() @@ -93,6 +95,16 @@ function Gamefield() { reset() }) + createEffect(() => { + function handleResize() { + const rect = frameRef.getBoundingClientRect() + setFrameSize({ x: rect.width, y: rect.height }) + } + handleResize() + window.addEventListener("resize", handleResize) + onCleanup(() => removeEventListener("resize", handleResize)) + }) + return (
{/* */} @@ -102,6 +114,7 @@ function Gamefield() { onMouseLeave={() => setMouseCursor((e) => ({ ...e, shouldShow: false })) } + ref={frameRef!} > @@ -118,7 +131,7 @@ function Gamefield() {
- + ) } diff --git a/leaky-ships/src/hooks/useDraw.tsx b/leaky-ships/src/hooks/useDraw.tsx index d83c456..3810845 100644 --- a/leaky-ships/src/hooks/useDraw.tsx +++ b/leaky-ships/src/hooks/useDraw.tsx @@ -1,20 +1,33 @@ import { createEffect, createSignal, onCleanup } from "solid-js" import { socket } from "~/lib/socket" -import { Draw, DrawLineProps, PlayerEvent, Point } from "../interfaces/frontend" -import { color, enable, shouldHide } from "./useDrawProps" +import { DrawLineProps, PlayerEvent, Point } from "../interfaces/frontend" +import { color, enable, frameSize, shouldHide } from "./useDrawProps" +import { useSession } from "./useSession" let canvasRef: HTMLCanvasElement +const strokes: Record<0 | 1, DrawLineProps[]> = { 0: [], 1: [] } + +function drawLine( + { prevPoint, currentPoint, color }: DrawLineProps, + ctx: CanvasRenderingContext2D, + i: 0 | 1, +) { + strokes[i].push({ prevPoint, currentPoint, color }) + + const currX = currentPoint.x * frameSize().x + const currY = currentPoint.y * frameSize().y + + const startPoint = prevPoint ?? currentPoint + const startX = startPoint.x * frameSize().x + const startY = startPoint.y * frameSize().y -function drawLine({ prevPoint, currentPoint, ctx, color }: Draw) { - const { x: currX, y: currY } = currentPoint const lineColor = color const lineWidth = 5 - const startPoint = prevPoint ?? currentPoint ctx.beginPath() ctx.lineWidth = lineWidth ctx.strokeStyle = lineColor - ctx.moveTo(startPoint.x, startPoint.y) + ctx.moveTo(startX, startY) ctx.lineTo(currX, currY) ctx.stroke() @@ -24,10 +37,12 @@ function drawLine({ prevPoint, currentPoint, ctx, color }: Draw) { ctx.fill() } -function clear() { +function clear(sIndex?: { i: 0 | 1 }) { const canvas = canvasRef if (!canvas) return + if (sIndex) strokes[sIndex.i] = [] + const ctx = canvas.getContext("2d") if (!ctx) return @@ -36,40 +51,51 @@ function clear() { export function DrawingCanvas() { const [mouseDown, setMouseDown] = createSignal(false) - - let prevPoint: null | Point - - const onMouseDown = () => setMouseDown(true) + const [stateIndex, setStateIndex] = createSignal<{ i: 0 | 1 } | null>(null) + const { selfIndex, selfIsActiveIndex, activeIndex } = useSession() createEffect(() => { + const i = activeIndex() + const canvas = canvasRef + if (!canvas || i === stateIndex()?.i) return + + const ctx = canvasRef?.getContext("2d") + if (!ctx) return + + clear() + strokes[i].forEach((props) => drawLine(props, ctx, i)) + setStateIndex({ i }) + }) + + createEffect(() => { + let prevPoint: null | Point + const canvas = canvasRef if (!canvas) return const handler = (e: MouseEvent) => { - if (!mouseDown()) return - const currentPoint = computePointInCanvas(e) + const sIndex = selfIndex() + if (!mouseDown() || !selfIsActiveIndex() || !sIndex) return + + const rect = canvas.getBoundingClientRect() + const x = (e.clientX - rect.left) / frameSize().x + const y = (e.clientY - rect.top) / frameSize().y + + const currentPoint = { x, y } const ctx = canvasRef?.getContext("2d") - if (!ctx || !currentPoint) return + if (!ctx) return const props = { currentPoint, - prevPoint: prevPoint, + prevPoint, color: color(), } socket.emit("draw-line", props) - drawLine({ ctx, ...props }) + drawLine(props, ctx, sIndex.i) prevPoint = currentPoint } - const computePointInCanvas = (e: MouseEvent) => { - const rect = canvas.getBoundingClientRect() - const x = e.clientX - rect.left - const y = e.clientY - rect.top - - return { x, y } - } - const mouseUpHandler = () => { setMouseDown(false) prevPoint = null @@ -87,30 +113,32 @@ export function DrawingCanvas() { }) createEffect(() => { + const sIndex = selfIndex() const canvas = canvasRef - if (!canvas) return + if (!canvas || !sIndex) return const ctx = canvas.getContext("2d") if (!ctx) return const playerEvent = (event: PlayerEvent) => { - if (!canvasRef?.toDataURL() || event.type !== "connect") return - console.log("sending canvas state") - socket.emit("canvas-state", canvasRef.toDataURL()) + if ( + !strokes[sIndex.i].length || + !selfIsActiveIndex() || + event.type !== "connect" + ) + return + console.log("Sending canvas state.") + socket.emit("canvas-state", strokes[sIndex.i]) } - const canvasStateFromServer = (state: string) => { - console.log("I received the state") - const img = new Image() - img.src = state - img.onload = () => { - ctx?.drawImage(img, 0, 0) - } + const canvasStateFromServer = (state: DrawLineProps[], i: 0 | 1) => { + console.log("Canvas state received.") + clear({ i }) + state.forEach((props) => drawLine(props, ctx, i)) } - const socketDrawLine = (props: DrawLineProps) => { - if (!ctx) return console.log("no ctx here") - drawLine({ ctx, ...props }) + const socketDrawLine = (props: DrawLineProps, i: 0 | 1) => { + drawLine(props, ctx, i) } socket.on("playerEvent", playerEvent) @@ -130,18 +158,22 @@ export function DrawingCanvas() { setMouseDown(true)} + width={frameSize().x} + height={frameSize().y} /> ) } -export function clearDrawing() { - clear() +export function clearDrawing(sIndex: { i: 0 | 1 } | null) { + if (!sIndex) return + clear(sIndex) socket.emit("canvas-clear") } diff --git a/leaky-ships/src/hooks/useDrawProps.ts b/leaky-ships/src/hooks/useDrawProps.ts index ba89601..06d91f6 100644 --- a/leaky-ships/src/hooks/useDrawProps.ts +++ b/leaky-ships/src/hooks/useDrawProps.ts @@ -1,4 +1,5 @@ import { createSignal } from "solid-js" +import { Position } from "~/interfaces/frontend" export const colors = [ "#ff4400", @@ -22,6 +23,10 @@ export const colors = [ export const [enable, setEnable] = createSignal(false) export const [shouldHide, setShouldHide] = createSignal(false) export const [color, setColor] = createSignal("#b32aa9") +export const [frameSize, setFrameSize] = createSignal({ + x: 648, + y: 648, +}) export function reset() { setEnable(false) diff --git a/leaky-ships/src/interfaces/ApiSocket.ts b/leaky-ships/src/interfaces/ApiSocket.ts index aa0fd70..8a18206 100644 --- a/leaky-ships/src/interfaces/ApiSocket.ts +++ b/leaky-ships/src/interfaces/ApiSocket.ts @@ -26,9 +26,9 @@ export interface ServerToClientEvents { isReady: (payload: { i: 0 | 1; isReady: boolean }) => void isConnected: (payload: { i: 0 | 1; isConnected: boolean }) => void "get-canvas-state": () => void - "canvas-state-from-server": (state: string, userIndex: 0 | 1) => void - "draw-line": (props: DrawLineProps, userIndex: 0 | 1) => void - "canvas-clear": () => void + "canvas-state-from-server": (state: DrawLineProps[], i: 0 | 1) => void + "draw-line": (props: DrawLineProps, i: 0 | 1) => void + "canvas-clear": (index: { i: 0 | 1 }) => void gameState: (newState: GameState) => void ships: (ships: ShipProps[], index: 0 | 1) => void dispatchMove: (props: MoveDispatchProps, i: 0 | 1) => void @@ -42,7 +42,7 @@ 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 + "canvas-state": (state: DrawLineProps[]) => void "draw-line": (props: DrawLineProps) => void "canvas-clear": () => void gameState: (newState: GameState) => void diff --git a/leaky-ships/src/interfaces/frontend.ts b/leaky-ships/src/interfaces/frontend.ts index c0b92bc..b7fe3ee 100644 --- a/leaky-ships/src/interfaces/frontend.ts +++ b/leaky-ships/src/interfaces/frontend.ts @@ -31,6 +31,7 @@ export interface ItemProps { amount?: number iconColor?: string disabled?: boolean + showWhen?: () => boolean enabled?: boolean callback?: () => void } @@ -54,9 +55,6 @@ export interface DrawLineProps { prevPoint: Point | null color: string } -export interface Draw extends DrawLineProps { - ctx: CanvasRenderingContext2D -} export interface ShipProps extends Position { size: number variant: number diff --git a/leaky-ships/src/routes/api/ws.ts b/leaky-ships/src/routes/api/ws.ts index 46aaf5b..1583bed 100644 --- a/leaky-ships/src/routes/api/ws.ts +++ b/leaky-ships/src/routes/api/ws.ts @@ -210,8 +210,8 @@ export async function GET({ }) socket.on("canvas-clear", () => { - if (!socket.data.gameId) return - socket.to(socket.data.gameId).emit("canvas-clear") + if (!socket.data.gameId || !socket.data.index) return + socket.to(socket.data.gameId).emit("canvas-clear", socket.data.index) }) socket.on("gameState", async (newState) => {