Working player wise drawing

This commit is contained in:
aronmal 2023-09-10 19:33:21 +02:00
parent d245009c37
commit c8a5c47b98
Signed by: aronmal
GPG key ID: 816B7707426FC612
7 changed files with 137 additions and 77 deletions

View file

@ -26,6 +26,7 @@ import { modes } from "~/lib/utils/helpers"
// import { Icons, toast } from "react-toastify" // import { Icons, toast } from "react-toastify"
import { For, Show, createEffect } from "solid-js" import { For, Show, createEffect } from "solid-js"
import { useNavigate } from "solid-start" import { useNavigate } from "solid-start"
import { clearDrawing } from "~/hooks/useDraw"
import { import {
color, color,
setEnable, setEnable,
@ -47,7 +48,7 @@ import { useSession } from "~/hooks/useSession"
import { EventBarModes } from "../../interfaces/frontend" import { EventBarModes } from "../../interfaces/frontend"
import Item from "./Item" import Item from "./Item"
function EventBar(props: { clear: () => void }) { function EventBar() {
const { selfIndex, selfIsActiveIndex, selfUser, ships } = useSession() const { selfIndex, selfIsActiveIndex, selfUser, ships } = useSession()
const navigator = useNavigate() const navigator = useNavigate()
@ -60,21 +61,24 @@ function EventBar(props: { clear: () => void }) {
setGameProps("menu", "menu") setGameProps("menu", "menu")
}, },
}, },
gameProps.gameState === "running" {
? { icon: faSwords,
icon: faSwords, text: "Attack",
text: "Attack", showWhen: () =>
callback: () => { gameProps.gameState === "running" &&
setGameProps("menu", "moves") (selfIsActiveIndex() || gameProps.menu !== "main"),
}, callback: () => {
} setGameProps("menu", "moves")
: { },
icon: faShip, },
text: "Ships", {
callback: () => { icon: faShip,
setGameProps("menu", "moves") text: "Ships",
}, showWhen: () => gameProps.gameState !== "running",
}, callback: () => {
setGameProps("menu", "moves")
},
},
{ {
icon: "pen", icon: "pen",
text: "Draw", text: "Draw",
@ -180,8 +184,18 @@ function EventBar(props: { clear: () => void }) {
}, },
], ],
draw: [ 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, icon: shouldHide() ? faEye : faEyeSlash,
text: shouldHide() ? "Show" : "Hide", text: shouldHide() ? "Show" : "Hide",
@ -301,10 +315,8 @@ function EventBar(props: { clear: () => void }) {
/> />
</Show> </Show>
<For each={items()[gameProps.menu]}> <For each={items()[gameProps.menu]}>
{(e, i) => ( {(e) => (
<Show <Show when={!e?.showWhen || e?.showWhen()}>
when={selfIsActiveIndex() || gameProps.menu !== "main" || i() !== 1}
>
<Item {...e} /> <Item {...e} />
</Show> </Show>
)} )}

View file

@ -7,7 +7,8 @@ import BorderTiles from "~/components/Gamefield/BorderTiles"
import EventBar from "~/components/Gamefield/EventBar" import EventBar from "~/components/Gamefield/EventBar"
import HitElems from "~/components/Gamefield/HitElems" import HitElems from "~/components/Gamefield/HitElems"
import Targets from "~/components/Gamefield/Targets" import Targets from "~/components/Gamefield/Targets"
import { DrawingCanvas, clearDrawing } from "~/hooks/useDraw" import { DrawingCanvas } from "~/hooks/useDraw"
import { setFrameSize } from "~/hooks/useDrawProps"
import { import {
full, full,
gameProps, gameProps,
@ -29,6 +30,7 @@ import Ships from "./Ships"
export const count = 12 export const count = 12
function Gamefield() { function Gamefield() {
let frameRef: HTMLDivElement
const { ships } = useSession() const { ships } = useSession()
const navigator = useNavigate() const navigator = useNavigate()
const { isConnected } = useSocket() const { isConnected } = useSocket()
@ -93,6 +95,16 @@ function Gamefield() {
reset() 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 ( return (
<div id="gamefield"> <div id="gamefield">
{/* <Bluetooth /> */} {/* <Bluetooth /> */}
@ -102,6 +114,7 @@ function Gamefield() {
onMouseLeave={() => onMouseLeave={() =>
setMouseCursor((e) => ({ ...e, shouldShow: false })) setMouseCursor((e) => ({ ...e, shouldShow: false }))
} }
ref={frameRef!}
> >
<BorderTiles /> <BorderTiles />
@ -118,7 +131,7 @@ function Gamefield() {
<DrawingCanvas /> <DrawingCanvas />
</div> </div>
<EventBar clear={clearDrawing} /> <EventBar />
</div> </div>
) )
} }

View file

@ -1,20 +1,33 @@
import { createEffect, createSignal, onCleanup } from "solid-js" import { createEffect, createSignal, onCleanup } from "solid-js"
import { socket } from "~/lib/socket" import { socket } from "~/lib/socket"
import { Draw, DrawLineProps, PlayerEvent, Point } from "../interfaces/frontend" import { DrawLineProps, PlayerEvent, Point } from "../interfaces/frontend"
import { color, enable, shouldHide } from "./useDrawProps" import { color, enable, frameSize, shouldHide } from "./useDrawProps"
import { useSession } from "./useSession"
let canvasRef: HTMLCanvasElement 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 lineColor = color
const lineWidth = 5 const lineWidth = 5
const startPoint = prevPoint ?? currentPoint
ctx.beginPath() ctx.beginPath()
ctx.lineWidth = lineWidth ctx.lineWidth = lineWidth
ctx.strokeStyle = lineColor ctx.strokeStyle = lineColor
ctx.moveTo(startPoint.x, startPoint.y) ctx.moveTo(startX, startY)
ctx.lineTo(currX, currY) ctx.lineTo(currX, currY)
ctx.stroke() ctx.stroke()
@ -24,10 +37,12 @@ function drawLine({ prevPoint, currentPoint, ctx, color }: Draw) {
ctx.fill() ctx.fill()
} }
function clear() { function clear(sIndex?: { i: 0 | 1 }) {
const canvas = canvasRef const canvas = canvasRef
if (!canvas) return if (!canvas) return
if (sIndex) strokes[sIndex.i] = []
const ctx = canvas.getContext("2d") const ctx = canvas.getContext("2d")
if (!ctx) return if (!ctx) return
@ -36,40 +51,51 @@ function clear() {
export function DrawingCanvas() { export function DrawingCanvas() {
const [mouseDown, setMouseDown] = createSignal(false) const [mouseDown, setMouseDown] = createSignal(false)
const [stateIndex, setStateIndex] = createSignal<{ i: 0 | 1 } | null>(null)
let prevPoint: null | Point const { selfIndex, selfIsActiveIndex, activeIndex } = useSession()
const onMouseDown = () => setMouseDown(true)
createEffect(() => { 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 const canvas = canvasRef
if (!canvas) return if (!canvas) return
const handler = (e: MouseEvent) => { const handler = (e: MouseEvent) => {
if (!mouseDown()) return const sIndex = selfIndex()
const currentPoint = computePointInCanvas(e) 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") const ctx = canvasRef?.getContext("2d")
if (!ctx || !currentPoint) return if (!ctx) return
const props = { const props = {
currentPoint, currentPoint,
prevPoint: prevPoint, prevPoint,
color: color(), color: color(),
} }
socket.emit("draw-line", props) socket.emit("draw-line", props)
drawLine({ ctx, ...props }) drawLine(props, ctx, sIndex.i)
prevPoint = currentPoint 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 = () => { const mouseUpHandler = () => {
setMouseDown(false) setMouseDown(false)
prevPoint = null prevPoint = null
@ -87,30 +113,32 @@ export function DrawingCanvas() {
}) })
createEffect(() => { createEffect(() => {
const sIndex = selfIndex()
const canvas = canvasRef const canvas = canvasRef
if (!canvas) return if (!canvas || !sIndex) return
const ctx = canvas.getContext("2d") const ctx = canvas.getContext("2d")
if (!ctx) return if (!ctx) return
const playerEvent = (event: PlayerEvent) => { const playerEvent = (event: PlayerEvent) => {
if (!canvasRef?.toDataURL() || event.type !== "connect") return if (
console.log("sending canvas state") !strokes[sIndex.i].length ||
socket.emit("canvas-state", canvasRef.toDataURL()) !selfIsActiveIndex() ||
event.type !== "connect"
)
return
console.log("Sending canvas state.")
socket.emit("canvas-state", strokes[sIndex.i])
} }
const canvasStateFromServer = (state: string) => { const canvasStateFromServer = (state: DrawLineProps[], i: 0 | 1) => {
console.log("I received the state") console.log("Canvas state received.")
const img = new Image() clear({ i })
img.src = state state.forEach((props) => drawLine(props, ctx, i))
img.onload = () => {
ctx?.drawImage(img, 0, 0)
}
} }
const socketDrawLine = (props: DrawLineProps) => { const socketDrawLine = (props: DrawLineProps, i: 0 | 1) => {
if (!ctx) return console.log("no ctx here") drawLine(props, ctx, i)
drawLine({ ctx, ...props })
} }
socket.on("playerEvent", playerEvent) socket.on("playerEvent", playerEvent)
@ -130,18 +158,22 @@ export function DrawingCanvas() {
<canvas <canvas
style={{ style={{
opacity: shouldHide() ? 0 : 1, opacity: shouldHide() ? 0 : 1,
"box-shadow": enable() ? "inset 0 0 0 2px " + color() : "none", "box-shadow":
enable() && selfIsActiveIndex()
? "inset 0 0 0 2px " + color()
: "none",
"pointer-events": enable() && !shouldHide() ? "auto" : "none", "pointer-events": enable() && !shouldHide() ? "auto" : "none",
}} }}
ref={canvasRef} ref={canvasRef}
onMouseDown={onMouseDown} onMouseDown={() => setMouseDown(true)}
width="648" width={frameSize().x}
height="648" height={frameSize().y}
/> />
) )
} }
export function clearDrawing() { export function clearDrawing(sIndex: { i: 0 | 1 } | null) {
clear() if (!sIndex) return
clear(sIndex)
socket.emit("canvas-clear") socket.emit("canvas-clear")
} }

View file

@ -1,4 +1,5 @@
import { createSignal } from "solid-js" import { createSignal } from "solid-js"
import { Position } from "~/interfaces/frontend"
export const colors = [ export const colors = [
"#ff4400", "#ff4400",
@ -22,6 +23,10 @@ export const colors = [
export const [enable, setEnable] = createSignal(false) export const [enable, setEnable] = createSignal(false)
export const [shouldHide, setShouldHide] = createSignal(false) export const [shouldHide, setShouldHide] = createSignal(false)
export const [color, setColor] = createSignal("#b32aa9") export const [color, setColor] = createSignal("#b32aa9")
export const [frameSize, setFrameSize] = createSignal<Position>({
x: 648,
y: 648,
})
export function reset() { export function reset() {
setEnable(false) setEnable(false)

View file

@ -26,9 +26,9 @@ export interface ServerToClientEvents {
isReady: (payload: { i: 0 | 1; isReady: boolean }) => void isReady: (payload: { i: 0 | 1; isReady: boolean }) => void
isConnected: (payload: { i: 0 | 1; isConnected: boolean }) => void isConnected: (payload: { i: 0 | 1; isConnected: boolean }) => void
"get-canvas-state": () => void "get-canvas-state": () => void
"canvas-state-from-server": (state: string, userIndex: 0 | 1) => void "canvas-state-from-server": (state: DrawLineProps[], i: 0 | 1) => void
"draw-line": (props: DrawLineProps, userIndex: 0 | 1) => void "draw-line": (props: DrawLineProps, i: 0 | 1) => void
"canvas-clear": () => void "canvas-clear": (index: { i: 0 | 1 }) => void
gameState: (newState: GameState) => void gameState: (newState: GameState) => void
ships: (ships: ShipProps[], index: 0 | 1) => void ships: (ships: ShipProps[], index: 0 | 1) => void
dispatchMove: (props: MoveDispatchProps, i: 0 | 1) => void dispatchMove: (props: MoveDispatchProps, i: 0 | 1) => void
@ -42,7 +42,7 @@ export interface ClientToServerEvents {
join: (withAck: (ack: boolean) => void) => void join: (withAck: (ack: boolean) => void) => void
gameSetting: (payload: GameSettings, callback: (hash: string) => void) => void gameSetting: (payload: GameSettings, callback: (hash: string) => void) => void
leave: (withAck: (ack: boolean) => void) => void leave: (withAck: (ack: boolean) => void) => void
"canvas-state": (state: string) => void "canvas-state": (state: DrawLineProps[]) => void
"draw-line": (props: DrawLineProps) => void "draw-line": (props: DrawLineProps) => void
"canvas-clear": () => void "canvas-clear": () => void
gameState: (newState: GameState) => void gameState: (newState: GameState) => void

View file

@ -31,6 +31,7 @@ export interface ItemProps {
amount?: number amount?: number
iconColor?: string iconColor?: string
disabled?: boolean disabled?: boolean
showWhen?: () => boolean
enabled?: boolean enabled?: boolean
callback?: () => void callback?: () => void
} }
@ -54,9 +55,6 @@ export interface DrawLineProps {
prevPoint: Point | null prevPoint: Point | null
color: string color: string
} }
export interface Draw extends DrawLineProps {
ctx: CanvasRenderingContext2D
}
export interface ShipProps extends Position { export interface ShipProps extends Position {
size: number size: number
variant: number variant: number

View file

@ -210,8 +210,8 @@ export async function GET({
}) })
socket.on("canvas-clear", () => { socket.on("canvas-clear", () => {
if (!socket.data.gameId) return if (!socket.data.gameId || !socket.data.index) return
socket.to(socket.data.gameId).emit("canvas-clear") socket.to(socket.data.gameId).emit("canvas-clear", socket.data.index)
}) })
socket.on("gameState", async (newState) => { socket.on("gameState", async (newState) => {