Fixed all eslint errors

This commit is contained in:
aronmal 2023-08-16 05:19:31 +02:00
parent fc7bf96b04
commit 9d8bb8e20b
Signed by: aronmal
GPG key ID: 816B7707426FC612
39 changed files with 709 additions and 758 deletions

View file

@ -2,5 +2,5 @@ import { PrismaClient } from "@prisma/client"
import "@total-typescript/ts-reset" import "@total-typescript/ts-reset"
declare global { declare global {
var prismaClient: PrismaClient let prismaClient: PrismaClient
} }

View file

@ -28,7 +28,6 @@
"classnames": "^2.3.2", "classnames": "^2.3.2",
"colors": "^1.4.0", "colors": "^1.4.0",
"http-status": "^1.6.2", "http-status": "^1.6.2",
"immer": "^10.0.2",
"nodemailer": "6.9.4", "nodemailer": "6.9.4",
"prisma": "^4.16.2", "prisma": "^4.16.2",
"socket.io": "^4.7.2", "socket.io": "^4.7.2",

View file

@ -1,10 +1,11 @@
import { defineConfig, devices } from "@playwright/test" import { defineConfig, devices } from "@playwright/test"
import dotenv from "dotenv"
/** /**
* Read environment variables from file. * Read environment variables from file.
* https://github.com/motdotla/dotenv * https://github.com/motdotla/dotenv
*/ */
require("dotenv").config() dotenv.config()
/** /**
* See https://playwright.dev/docs/test-configuration. * See https://playwright.dev/docs/test-configuration.

View file

@ -56,9 +56,6 @@ dependencies:
http-status: http-status:
specifier: ^1.6.2 specifier: ^1.6.2
version: 1.6.2 version: 1.6.2
immer:
specifier: ^10.0.2
version: 10.0.2
nodemailer: nodemailer:
specifier: 6.9.4 specifier: 6.9.4
version: 6.9.4 version: 6.9.4
@ -3521,10 +3518,6 @@ packages:
engines: {node: '>= 4'} engines: {node: '>= 4'}
dev: true dev: true
/immer@10.0.2:
resolution: {integrity: sha512-Rx3CqeqQ19sxUtYV9CU911Vhy8/721wRFnJv3REVGWUmoAcIwzifTsdmJte/MV+0/XpM35LZdQMBGkRIoLPwQA==}
dev: false
/immutable@4.3.2: /immutable@4.3.2:
resolution: {integrity: sha512-oGXzbEDem9OOpDWZu88jGiYCvIsLHMvGw+8OXlpsvTFvIQplQbjg1B1cvKg8f7Hoch6+NGjpPsH1Fr+Mc2D1aA==} resolution: {integrity: sha512-oGXzbEDem9OOpDWZu88jGiYCvIsLHMvGw+8OXlpsvTFvIQplQbjg1B1cvKg8f7Hoch6+NGjpPsH1Fr+Mc2D1aA==}
@ -4789,7 +4782,7 @@ packages:
zustand: ^4.3.0 zustand: ^4.3.0
dependencies: dependencies:
solid-js: 1.7.11 solid-js: 1.7.11
zustand: 4.4.1(immer@10.0.2)(react@18.2.0) zustand: 4.4.1(react@18.2.0)
dev: false dev: false
/source-map-js@1.0.2: /source-map-js@1.0.2:
@ -5375,7 +5368,7 @@ packages:
resolution: {integrity: sha512-+dTu2m6gmCbO9Ahm4ZBDapx2O6ZY9QSPXst2WXjcznPMwf2YNpn3RevLx4KkZp1OPW/ouFcoBtBzFz/LeY69oA==} resolution: {integrity: sha512-+dTu2m6gmCbO9Ahm4ZBDapx2O6ZY9QSPXst2WXjcznPMwf2YNpn3RevLx4KkZp1OPW/ouFcoBtBzFz/LeY69oA==}
dev: false dev: false
/zustand@4.4.1(immer@10.0.2)(react@18.2.0): /zustand@4.4.1(react@18.2.0):
resolution: {integrity: sha512-QCPfstAS4EBiTQzlaGP1gmorkh/UL1Leaj2tdj+zZCZ/9bm0WS7sI2wnfD5lpOszFqWJ1DcPnGoY8RDL61uokw==} resolution: {integrity: sha512-QCPfstAS4EBiTQzlaGP1gmorkh/UL1Leaj2tdj+zZCZ/9bm0WS7sI2wnfD5lpOszFqWJ1DcPnGoY8RDL61uokw==}
engines: {node: '>=12.7.0'} engines: {node: '>=12.7.0'}
peerDependencies: peerDependencies:
@ -5390,7 +5383,6 @@ packages:
react: react:
optional: true optional: true
dependencies: dependencies:
immer: 10.0.2
react: 18.2.0 react: 18.2.0
use-sync-external-store: 1.2.0(react@18.2.0) use-sync-external-store: 1.2.0(react@18.2.0)
dev: false dev: false

View file

@ -48,7 +48,7 @@ function Bluetooth() {
if (!isWebBluetoothEnabled()) return if (!isWebBluetoothEnabled()) return
return getDeviceInfo() return getDeviceInfo()
.then(connectGatt) .then(connectGatt)
.then((_) => { .then(() => {
console.log("Reading UV Index...") console.log("Reading UV Index...")
return gattCharacteristic.readValue() return gattCharacteristic.readValue()
}) })
@ -108,7 +108,7 @@ function Bluetooth() {
if (!isWebBluetoothEnabled()) return if (!isWebBluetoothEnabled()) return
gattCharacteristic gattCharacteristic
.startNotifications() .startNotifications()
.then((_) => { .then(() => {
console.log("Start reading...") console.log("Start reading...")
setStartDisabled(true) setStartDisabled(true)
setStopDisabled(false) setStopDisabled(false)
@ -119,7 +119,7 @@ function Bluetooth() {
if (!isWebBluetoothEnabled()) return if (!isWebBluetoothEnabled()) return
gattCharacteristic gattCharacteristic
.stopNotifications() .stopNotifications()
.then((_) => { .then(() => {
console.log("Stop reading...") console.log("Stop reading...")
setStartDisabled(false) setStartDisabled(false)
setStopDisabled(true) setStopDisabled(true)
@ -141,7 +141,7 @@ function Bluetooth() {
<button <button
id="start" id="start"
class="bluetooth" class="bluetooth"
disabled={startDisabled} disabled={startDisabled()}
onClick={start} onClick={start}
> >
Start Start
@ -149,7 +149,7 @@ function Bluetooth() {
<button <button
id="stop" id="stop"
class="bluetooth" class="bluetooth"
disabled={stopDisabled} disabled={stopDisabled()}
onClick={stop} onClick={stop}
> >
Stop Stop

View file

@ -1,9 +1,6 @@
import classNames from "classnames" import classNames from "classnames"
function BurgerMenu({ function BurgerMenu(props: {
onClick,
blur,
}: {
onClick?: () => void onClick?: () => void
blur?: boolean blur?: boolean
}) { }) {
@ -12,9 +9,9 @@ function BurgerMenu({
id="menu" id="menu"
class={classNames( class={classNames(
"absolute left-4 top-4 flex h-16 w-16 items-center justify-center rounded-lg border-b-2 border-shield-gray bg-grayish shadow-lg duration-100 active:border-b-0 active:border-t-2 md:left-6 md:top-6 md:h-20 md:w-20 md:rounded-xl md:border-b-4 md:active:border-t-4 lg:left-8 lg:top-8 xl:left-12 xl:top-12 xl:h-24 xl:w-24", "absolute left-4 top-4 flex h-16 w-16 items-center justify-center rounded-lg border-b-2 border-shield-gray bg-grayish shadow-lg duration-100 active:border-b-0 active:border-t-2 md:left-6 md:top-6 md:h-20 md:w-20 md:rounded-xl md:border-b-4 md:active:border-t-4 lg:left-8 lg:top-8 xl:left-12 xl:top-12 xl:h-24 xl:w-24",
{ "blur-sm": blur }, { "blur-sm": props.blur },
)} )}
onClick={() => onClick && setTimeout(onClick, 200)} onClick={() => props.onClick && setTimeout(props.onClick, 200)}
> >
<img <img
class="pixelart h-12 w-12 md:h-16 md:w-16 xl:h-20 xl:w-20" class="pixelart h-12 w-12 md:h-16 md:w-16 xl:h-20 xl:w-20"

View file

@ -1,3 +1,4 @@
import { For } from "solid-js"
import { useGameProps } from "~/hooks/useGameProps" import { useGameProps } from "~/hooks/useGameProps"
import useIndex from "~/hooks/useIndex" import useIndex from "~/hooks/useIndex"
import useShips from "~/hooks/useShips" import useShips from "~/hooks/useShips"
@ -59,7 +60,7 @@ function BorderTiles() {
} }
} }
let tilesProperties: TilesType[] = [] const tilesProperties: TilesType[] = []
for (let y = 0; y < count + 2; y++) { for (let y = 0; y < count + 2; y++) {
for (let x = 0; x < count + 2; x++) { for (let x = 0; x < count + 2; x++) {
@ -82,55 +83,53 @@ function BorderTiles() {
} }
return ( return (
<> <For each={tilesProperties}>
{tilesProperties.map(({ key, className, isGameTile, x, y }) => { {(props) => (
return ( <div
<div class={props.className}
class={className} style={{ "--x": props.x, "--y": props.y }}
style={{ "--x": x, "--y": y }} onClick={() => {
onClick={() => { if (payload?.game?.state === "running") {
if (payload?.game?.state === "running") { settingTarget(props.isGameTile, props.x, props.y)
settingTarget(isGameTile, x, y) } else if (payload?.game?.state === "starting") {
} else if (payload?.game?.state === "starting") { const { index } = intersectingShip(ships(), {
const { index } = intersectingShip(ships(), { ...mouseCursor,
...mouseCursor, size: 1,
size: 1, variant: 0,
variant: 0, orientation: "h",
orientation: "h",
})
if (typeof index === "undefined")
settingTarget(isGameTile, x, y)
else {
const ship = ships()[index]
useGameProps.setState({ mode: ship.size - 2 })
removeShip(ship)
setMouseCursor((e) => ({ ...e, shouldShow: true }))
}
}
}}
onMouseEnter={() =>
setMouseCursor({
x,
y,
shouldShow:
isGameTile &&
(payload?.game?.state === "starting"
? intersectingShip(
ships(),
shipProps(ships(), mode, {
x,
y,
orientation: targetPreview.orientation,
}),
true,
).score < 2
: true),
}) })
if (typeof index === "undefined")
settingTarget(props.isGameTile, props.x, props.y)
else {
const ship = ships()[index]
useGameProps.setState({ mode: ship.size - 2 })
removeShip(ship)
setMouseCursor((e) => ({ ...e, shouldShow: true }))
}
} }
/> }}
) onMouseEnter={() =>
})} setMouseCursor({
</> x: props.x,
y: props.y,
shouldShow:
props.isGameTile &&
(payload?.game?.state === "starting"
? intersectingShip(
ships(),
shipProps(ships(), mode, {
x: props.x,
y: props.y,
orientation: targetPreview.orientation,
}),
true,
).score < 2
: true),
})
}
/>
)}
</For>
) )
} }

View file

@ -25,7 +25,7 @@ import { socket } from "~/lib/socket"
import { modes } from "~/lib/utils/helpers" import { modes } from "~/lib/utils/helpers"
import { GamePropsSchema } from "~/lib/zodSchemas" import { GamePropsSchema } from "~/lib/zodSchemas"
// import { Icons, toast } from "react-toastify" // import { Icons, toast } from "react-toastify"
import { createEffect, createMemo } from "solid-js" import { For, Show, createEffect } from "solid-js"
import { useNavigate } from "solid-start" import { useNavigate } from "solid-start"
import { useDrawProps } from "~/hooks/useDrawProps" import { useDrawProps } from "~/hooks/useDrawProps"
import { useGameProps } from "~/hooks/useGameProps" import { useGameProps } from "~/hooks/useGameProps"
@ -49,7 +49,7 @@ export function setGameSetting(
} }
} }
function EventBar({ clear }: { clear: () => void }) { function EventBar(props: { clear: () => void }) {
const { shouldHide, color } = useDrawProps() const { shouldHide, color } = useDrawProps()
const { selfIndex, isActiveIndex, selfUser } = useIndex() const { selfIndex, isActiveIndex, selfUser } = useIndex()
const { ships } = useShips() const { ships } = useShips()
@ -70,7 +70,7 @@ function EventBar({ clear }: { clear: () => void }) {
const gameSetting = (payload: GameSettings) => const gameSetting = (payload: GameSettings) =>
setGameSetting(payload, setSetting, full) setGameSetting(payload, setSetting, full)
const items = createMemo<EventBarModes>(() => ({ const items = (): EventBarModes => ({
main: [ main: [
{ {
icon: "burger-menu", icon: "burger-menu",
@ -199,7 +199,7 @@ function EventBar({ clear }: { clear: () => void }) {
}, },
], ],
draw: [ draw: [
{ icon: faBroomWide, text: "Clear", callback: clear }, { icon: faBroomWide, text: "Clear", callback: props.clear },
{ icon: faPalette, text: "Color", iconColor: color }, { icon: faPalette, text: "Color", iconColor: color },
{ {
icon: shouldHide ? faEye : faEyeSlash, icon: shouldHide ? faEye : faEyeSlash,
@ -261,7 +261,7 @@ function EventBar({ clear }: { clear: () => void }) {
}, },
}, },
], ],
})) })
createEffect(() => { createEffect(() => {
if ( if (
@ -277,39 +277,39 @@ function EventBar({ clear }: { clear: () => void }) {
createEffect(() => { createEffect(() => {
useDrawProps.setState({ enable: menu === "draw" }) useDrawProps.setState({ enable: menu === "draw" })
}, [menu])
createEffect(() => {
if (payload?.game?.state !== "running") return
let toastId = "otherPlayer"
// if (isActiveIndex) toast.dismiss(toastId)
// else
// toast.info("Waiting for other player...", {
// toastId,
// position: "top-right",
// icon: Icons.spinner(),
// autoClose: false,
// hideProgressBar: true,
// closeButton: false,
// })
// toastId = "connect_error"
// const isActive = toast.isActive(toastId)
// console.log(toastId, isActive)
// if (isActive)
// toast.update(toastId, {
// autoClose: 5000,
// })
// else
// toast.warn("Spie", { toastId })
}) })
// createEffect(() => {
// if (payload?.game?.state !== "running") return
// const toastId = "otherPlayer"
// if (isActiveIndex) toast.dismiss(toastId)
// else
// toast.info("Waiting for other player...", {
// toastId,
// position: "top-right",
// icon: Icons.spinner(),
// autoClose: false,
// hideProgressBar: true,
// closeButton: false,
// })
// toastId = "connect_error"
// const isActive = toast.isActive(toastId)
// console.log(toastId, isActive)
// if (isActive)
// toast.update(toastId, {
// autoClose: 5000,
// })
// else
// toast.warn("Spie", { toastId })
// })
return ( return (
<div class="event-bar"> <div class="event-bar">
{menu !== "main" ? ( <Show when={menu !== "main"}>
<Item <Item
props={{ {...{
icon: faReply, icon: faReply,
text: "Return", text: "Return",
iconColor: "#555", iconColor: "#555",
@ -318,20 +318,23 @@ function EventBar({ clear }: { clear: () => void }) {
}, },
}} }}
/> />
) : null} </Show>
{items()[menu].map((e, i) => { <For each={items()[menu]}>
if (!isActiveIndex && menu === "main" && i === 1) return {(e, i) => (
return <Item props={e} /> <Show when={isActiveIndex && menu !== "main" && i() !== 1}>
})} <Item {...e} />
{menu === "moves" ? ( </Show>
)}
</For>
<Show when={menu === "moves"}>
<Item <Item
props={{ {...{
icon: icon:
selfIndex >= 0 && userStates[selfIndex].isReady selfIndex >= 0 && userStates[selfIndex].isReady()
? faLock ? faLock
: faCheck, : faCheck,
text: text:
selfIndex >= 0 && userStates[selfIndex].isReady selfIndex >= 0 && userStates[selfIndex].isReady()
? "unready" ? "unready"
: "Done", : "Done",
disabled: disabled:
@ -365,7 +368,7 @@ function EventBar({ clear }: { clear: () => void }) {
}, },
}} }}
/> />
) : null} </Show>
</div> </div>
) )
} }

View file

@ -35,7 +35,7 @@ function Gamefield() {
} = useGameProps() } = useGameProps()
const { isConnected } = useSocket() const { isConnected } = useSocket()
const { canvasRef, onMouseDown, clear } = useDraw() const usingDraw = useDraw()
const { enable, color, shouldHide } = useDrawProps() const { enable, color, shouldHide } = useDrawProps()
createEffect(() => { createEffect(() => {
@ -46,12 +46,12 @@ function Gamefield() {
return return
socket.emit("ships", selfUser?.ships ?? []) socket.emit("ships", selfUser?.ships ?? [])
socket.emit("gameState", "running") socket.emit("gameState", "running")
}, [payload?.game?.state, selfUser?.ships, userStates]) })
createEffect(() => { createEffect(() => {
if (payload?.game?.id || !isConnected) return if (payload?.game?.id || !isConnected) return
socket.emit("update", full) socket.emit("update", full)
}, [full, payload?.game?.id, isConnected]) })
createEffect(() => { createEffect(() => {
if (mode < 0) return if (mode < 0) return
@ -85,7 +85,7 @@ function Gamefield() {
document.removeEventListener("keydown", handleKeyPress) document.removeEventListener("keydown", handleKeyPress)
} }
} }
}, [mode, mouseCursor, payload?.game?.state, setTargetPreview, target]) })
createEffect(() => { createEffect(() => {
if (payload?.game?.state !== "aborted") return if (payload?.game?.state !== "aborted") return
@ -129,13 +129,13 @@ function Gamefield() {
"box-shadow": enable ? "inset 0 0 0 2px " + color : "none", "box-shadow": enable ? "inset 0 0 0 2px " + color : "none",
"pointer-events": enable && !shouldHide ? "auto" : "none", "pointer-events": enable && !shouldHide ? "auto" : "none",
}} }}
ref={canvasRef} ref={usingDraw.canvasRef}
onMouseDown={onMouseDown} onMouseDown={usingDraw.onMouseDown}
width="648" width="648"
height="648" height="648"
/> />
</div> </div>
<EventBar clear={clear} /> <EventBar clear={usingDraw.clear} />
</div> </div>
) )
} }

View file

@ -5,25 +5,29 @@ import {} from "solid-js"
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon" import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
import { PointerProps } from "../../interfaces/frontend" import { PointerProps } from "../../interfaces/frontend"
function GamefieldPointer({ function GamefieldPointer(
props: { x, y, show, type, edges, imply }, props: PointerProps & {
preview, preview?: boolean
}: { },
props: PointerProps ) {
preview?: boolean const isRadar = () => props.type === "radar"
}) { const style = () =>
const isRadar = type === "radar" !(isRadar() && !props.edges.filter((s) => s).length)
const style = !(isRadar && !edges.filter((s) => s).length) ? { "--x": props.x, "--y": props.y }
? { "--x": x, "--y": y } : {
: { "--x1": x - 1, "--x2": x + 2, "--y1": y - 1, "--y2": y + 2 } "--x1": props.x - 1,
"--x2": props.x + 2,
"--y1": props.y - 1,
"--y2": props.y + 2,
}
return ( return (
<div <div
class={classNames("hit-svg", "target", type, ...edges, { class={classNames("hit-svg", "target", props.type, ...props.edges, {
preview: preview, preview: props.preview,
show: show, show: props.show,
imply: imply, imply: props.imply,
})} })}
style={style} style={style()}
> >
<FontAwesomeIcon icon={!isRadar ? faCrosshairs : faRadar} /> <FontAwesomeIcon icon={!isRadar ? faCrosshairs : faRadar} />
</div> </div>

View file

@ -1,28 +1,27 @@
import { faBurst, faXmark } from "@fortawesome/pro-solid-svg-icons" import { faBurst, faXmark } from "@fortawesome/pro-solid-svg-icons"
import { For } from "solid-js"
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon" import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
import useIndex from "~/hooks/useIndex" import useIndex from "~/hooks/useIndex"
import { Hit } from "../../interfaces/frontend" import { Hit } from "../../interfaces/frontend"
function HitElems({ function HitElems(props: { hits?: Hit[]; colorOverride?: string }) {
props,
}: {
props?: { hits: Hit[]; colorOverride?: string }
}) {
const { activeUser } = useIndex() const { activeUser } = useIndex()
const hits = () => props?.hits
const colorOverride = () => props?.colorOverride
return ( return (
<> <For each={hits() ?? activeUser?.hits ?? []}>
{(props?.hits ?? activeUser?.hits ?? []).map(({ hit, x, y }, i) => ( {(props) => (
<div class="hit-svg" style={{ "--x": x, "--y": y }}> <div class="hit-svg" style={{ "--x": props.x, "--y": props.y }}>
<FontAwesomeIcon <FontAwesomeIcon
icon={hit ? faBurst : faXmark} icon={props.hit ? faBurst : faXmark}
style={{ style={{
color: props?.colorOverride || (hit ? "red" : undefined), color: colorOverride() || (props.hit ? "red" : undefined),
}} }}
/> />
</div> </div>
))} )}
</> </For>
) )
} }

View file

@ -1,17 +1,13 @@
import { createEffect, createSignal } from "solid-js" import { createEffect, createSignal } from "solid-js"
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon" import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
import { useDrawProps } from "~/hooks/useDrawProps" // import { useDrawProps } from "~/hooks/useDrawProps"
// import { HexColorPicker } from "react-colorful" // import { HexColorPicker } from "react-colorful"
import classNames from "classnames" import classNames from "classnames"
import { ItemProps } from "../../interfaces/frontend" import { ItemProps } from "../../interfaces/frontend"
function Item({ function Item(props: ItemProps) {
props: { icon, text, amount, iconColor, disabled, enabled, callback }, const isColor = () => props.text === "Color"
}: { // const { color, setColor } = useDrawProps()
props: ItemProps
}) {
const isColor = text === "Color"
const { color, setColor } = useDrawProps()
const [active, setActive] = createSignal(false) const [active, setActive] = createSignal(false)
let cpRef: HTMLDivElement let cpRef: HTMLDivElement
@ -21,18 +17,23 @@ function Item({
} }
// Add event listeners // Add event listeners
if (!isColor) return if (!isColor()) return
setTimeout(() => window.addEventListener("click", inActive), 200) setTimeout(() => window.addEventListener("click", inActive), 200)
// Remove event listeners // Remove event listeners
return () => { return () => {
window.removeEventListener("click", inActive) window.removeEventListener("click", inActive)
} }
}, [active, isColor]) })
return ( return (
<div class="item" onClick={isColor ? () => setActive(true) : callback}> <div
{isColor ? ( class="item"
onClick={() =>
isColor() ? setActive(true) : props.callback && props.callback()
}
>
{isColor() ? (
<div <div
ref={cpRef!} ref={cpRef!}
class={classNames("react-colorful-wrapper", { active: active })} class={classNames("react-colorful-wrapper", { active: active })}
@ -42,29 +43,32 @@ function Item({
) : null} ) : null}
<div <div
class={classNames("container", { class={classNames("container", {
amount: typeof amount !== "undefined", amount: typeof props.amount !== "undefined",
disabled: disabled || amount === 0, disabled: props.disabled || props.amount === 0,
enabled: disabled === false || enabled, enabled: props.disabled === false || props.enabled,
})} })}
style={ style={
typeof amount !== "undefined" typeof props.amount !== "undefined"
? { ? {
"--amount": JSON.stringify(amount.toString()), "--amount": JSON.stringify(props.amount.toString()),
} }
: {} : {}
} }
> >
{typeof icon === "string" ? ( {typeof props.icon === "string" ? (
<img <img
src={`/assets/${icon}.png`} src={`/assets/${props.icon}.png`}
alt={`${icon}.png`} alt={`${props.icon}.png`}
class="pixelart" class="pixelart"
/> />
) : ( ) : (
<FontAwesomeIcon icon={icon} color={iconColor ?? "#444"} /> <FontAwesomeIcon
icon={props.icon}
color={props.iconColor ?? "#444"}
/>
)} )}
</div> </div>
<span>{text}</span> <span>{props.text}</span>
</div> </div>
) )
} }

View file

@ -1,5 +1,5 @@
import classNames from "classnames" import classNames from "classnames"
import {} from "solid-js" import { For } from "solid-js"
import { fieldIndex } from "~/lib/utils/helpers" import { fieldIndex } from "~/lib/utils/helpers"
import { Field } from "../../interfaces/frontend" import { Field } from "../../interfaces/frontend"
import { count } from "./Gamefield" import { count } from "./Gamefield"
@ -34,16 +34,16 @@ function Labeling() {
(a, b) => fieldIndex(count, a.x, a.y) - fieldIndex(count, b.x, b.y), (a, b) => fieldIndex(count, a.x, a.y) - fieldIndex(count, b.x, b.y),
) )
return ( return (
<> <For each={elems}>
{elems.map(({ field, x, y, orientation }, i) => ( {(props) => (
<span <span
class={classNames("label", orientation, field)} class={classNames("label", props.orientation, props.field)}
style={{ "--x": x, "--y": y }} style={{ "--x": props.x, "--y": props.y }}
> >
{field} {props.field}
</span> </span>
))} )}
</> </For>
) )
} }

View file

@ -8,18 +8,14 @@ const sizes: { [n: number]: number } = {
4: 196, 4: 196,
} }
function Ship({ function Ship(
props: { size, variant, x, y, orientation }, props: ShipProps & {
preview, preview?: boolean
warn, warn?: boolean
color, color?: string
}: { },
props: ShipProps ) {
preview?: boolean const filename = () => `ship_blue_${props.size}x_${props.variant}.gif`
warn?: boolean
color?: string
}) {
const filename = `ship_blue_${size}x_${variant}.gif`
let canvasRef: HTMLCanvasElement let canvasRef: HTMLCanvasElement
createEffect(() => { createEffect(() => {
@ -27,30 +23,40 @@ function Ship({
const ctx = canvas?.getContext("2d") const ctx = canvas?.getContext("2d")
if (!canvas || !ctx) return if (!canvas || !ctx) return
const gif = new Image() const gif = new Image()
gif.src = "/assets/" + filename gif.src = "/assets/" + filename()
// Load the GIF and start rendering // Load the GIF and start rendering
gif.onload = function () { gif.onload = function () {
// Set the canvas size to match the GIF dimensions // Set the canvas size to match the GIF dimensions
canvas.width = orientation === "h" ? sizes[size] : 48 canvas.width = props.orientation === "h" ? sizes[props.size] : 48
canvas.height = orientation === "v" ? sizes[size] : 48 canvas.height = props.orientation === "v" ? sizes[props.size] : 48
if (orientation === "v") if (props.orientation === "v")
// Rotate the canvas by 90 degrees // Rotate the canvas by 90 degrees
ctx.rotate((90 * Math.PI) / 180) ctx.rotate((90 * Math.PI) / 180)
// Draw the rotated GIF // Draw the rotated GIF
ctx.drawImage(gif, 0, orientation === "h" ? 0 : -48, sizes[size], 48) ctx.drawImage(
gif,
0,
props.orientation === "h" ? 0 : -48,
sizes[props.size],
48,
)
} }
}, [filename, orientation, size, x, y]) })
return ( return (
<div <div
class={classNames("ship", "s" + size, orientation, { class={classNames("ship", "s" + props.size, props.orientation, {
preview: preview, preview: props.preview,
warn: warn, warn: props.warn,
})} })}
style={{ "--x": x, "--y": y, "--color": color ?? "limegreen" }} style={{
"--x": props.x,
"--y": props.y,
"--color": props.color ?? "limegreen",
}}
> >
<canvas ref={canvasRef!} /> <canvas ref={canvasRef!} />
</div> </div>

View file

@ -1,3 +1,4 @@
import { For, Show } from "solid-js"
import { useGameProps } from "~/hooks/useGameProps" import { useGameProps } from "~/hooks/useGameProps"
import useIndex from "~/hooks/useIndex" import useIndex from "~/hooks/useIndex"
import Ship from "./Ship" import Ship from "./Ship"
@ -6,9 +7,11 @@ function Ships() {
const { payload } = useGameProps() const { payload } = useGameProps()
const { isActiveIndex, selfUser } = useIndex() const { isActiveIndex, selfUser } = useIndex()
if (payload?.game?.state === "running" && isActiveIndex) return null return (
<Show when={payload?.game?.state === "running" && isActiveIndex}>
return <>{selfUser?.ships.map((props, i) => <Ship props={props} />)}</> <For each={selfUser?.ships}>{(props) => <Ship {...props} />}</For>
</Show>
)
} }
export default Ships export default Ships

View file

@ -1,3 +1,4 @@
import { For, Match, Switch } from "solid-js"
import { useGameProps } from "~/hooks/useGameProps" import { useGameProps } from "~/hooks/useGameProps"
import useIndex from "~/hooks/useIndex" import useIndex from "~/hooks/useIndex"
import useShips from "~/hooks/useShips" import useShips from "~/hooks/useShips"
@ -15,54 +16,40 @@ function Targets() {
const { payload, target, targetPreview, mode } = useGameProps() const { payload, target, targetPreview, mode } = useGameProps()
const { ships } = useShips() const { ships } = useShips()
switch (payload?.game?.state) { const ship = shipProps(ships(), mode, targetPreview)
case "running": const { fields, borders, score } = intersectingShip(ships(), ship)
return (
<>
{[
...composeTargetTiles(target, mode, activeUser?.hits ?? []).map(
(props, i) => <GamefieldPointer props={props} />,
),
...composeTargetTiles(
targetPreview,
mode,
activeUser?.hits ?? [],
).map((props, i) => <GamefieldPointer props={props} preview />),
]}
</>
)
case "starting": return (
if (mode < 0 && !targetPreview.show) return null <Switch>
const ship = shipProps(ships(), mode, targetPreview) <Match when={payload?.game?.state === "running"}>
const { fields, borders, score } = intersectingShip(ships(), ship) <For each={composeTargetTiles(target, mode, activeUser?.hits ?? [])}>
return ( {(props) => <GamefieldPointer {...props} />}
<> </For>
<Ship <For
preview each={composeTargetTiles(targetPreview, mode, activeUser?.hits ?? [])}
warn={score > 0} >
color={ {(props) => <GamefieldPointer {...props} preview />}
fields.length ? "red" : borders.length ? "orange" : undefined </For>
} </Match>
props={ship} <Match
/> when={
<HitElems payload?.game?.state === "starting" && mode >= 0 && targetPreview.show
props={{ }
hits: fields.map((e, i) => ({ ...e, i, hit: true })), >
}} <Ship
/> {...ship}
<HitElems preview
props={{ warn={score > 0}
hits: borders.map((e, i) => ({ ...e, i, hit: true })), color={fields.length ? "red" : borders.length ? "orange" : undefined}
colorOverride: "orange", />
}} <HitElems hits={fields.map((e, i) => ({ ...e, i, hit: true }))} />
/> <HitElems
</> hits={borders.map((e, i) => ({ ...e, i, hit: true }))}
) colorOverride={"orange"}
/>
default: </Match>
return null </Switch>
} )
} }
export default Targets export default Targets

View file

@ -1,5 +1,5 @@
import classNames from "classnames" import classNames from "classnames"
import { createEffect, createMemo, createSignal } from "solid-js" import { For, createEffect, createSignal } from "solid-js"
function Grid() { function Grid() {
function floorClient(number: number) { function floorClient(number: number) {
@ -24,7 +24,7 @@ function Grid() {
} }
handleResize() handleResize()
window.addEventListener("resize", handleResize) window.addEventListener("resize", handleResize)
}, []) })
createEffect(() => { createEffect(() => {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
@ -37,80 +37,77 @@ function Grid() {
return () => clearTimeout(timeout) return () => clearTimeout(timeout)
}) })
const createTiles = createMemo(() => { function Tile(props: { index: number }) {
const colors = [ const x = () => props.index % params().columns
"rgb(229, 57, 53)", const y = () => Math.floor(props.index / params().columns)
"rgb(253, 216, 53)", const xDiff = () => (x() - position()[0]) / 20
"rgb(244, 81, 30)", const yDiff = () => (y() - position()[1]) / 20
"rgb(76, 175, 80)", const pos = () =>
"rgb(33, 150, 243)", Math.sqrt(xDiff() * xDiff() + yDiff() * yDiff()).toFixed(2)
"rgb(156, 39, 176)",
]
function createTile(index: number) { function doEffect(posX: number, posY: number) {
const x = index % params().columns if (active()) return
const y = Math.floor(index / params().columns) setPosition([posX, posY])
const xDiff = (x - position()[0]) / 20 setActve(true)
const yDiff = (y - position()[1]) / 20
const pos = Math.sqrt(xDiff * xDiff + yDiff * yDiff).toFixed(2)
function doEffect(posX: number, posY: number) { function xDiff(x: number) {
if (active()) return return (x - posX) / 20
setPosition([posX, posY])
setActve(true)
function xDiff(x: number) {
return (x - posX) / 20
}
function yDiff(y: number) {
return (y - posY) / 20
}
function pos(x: number, y: number) {
return Math.sqrt(xDiff(x) * xDiff(x) + yDiff(y) * yDiff(y))
}
const diagonals = [
pos(0, 0),
pos(params().columns, 0),
pos(0, params().rows),
pos(params().columns, params().rows),
]
setTimeout(
() => {
setActve(false)
setCount((e) => e + 1)
},
Math.max(...diagonals) * 1000 + 300,
)
} }
function yDiff(y: number) {
return (y - posY) / 20
}
function pos(x: number, y: number) {
return Math.sqrt(xDiff(x) * xDiff(x) + yDiff(y) * yDiff(y))
}
const diagonals = [
pos(0, 0),
pos(params().columns, 0),
pos(0, params().rows),
pos(params().columns, params().rows),
]
return ( setTimeout(
<div () => {
class={classNames({ tile: true, active: active() })} setActve(false)
style={{ "--delay": pos + "s" }} setCount((e) => e + 1)
onClick={() => doEffect(x, y)} },
/> Math.max(...diagonals) * 1000 + 300,
) )
} }
return ( return (
<div <div
id="tiles" class={classNames({ tile: true, active: active() })}
style={{ style={{ "--delay": pos() + "s" }}
"--columns": params().columns, onClick={() => doEffect(x(), y())}
"--rows": params().rows, />
"--bg-color-1": colors[count() % colors.length],
"--bg-color-2": colors[(count() + 1) % colors.length],
}}
>
{Array.from(Array(params().quantity), (_tile, index) =>
createTile(index),
)}
</div>
) )
}, [params, position, active, count]) }
return createTiles() const colors = [
"rgb(229, 57, 53)",
"rgb(253, 216, 53)",
"rgb(244, 81, 30)",
"rgb(76, 175, 80)",
"rgb(33, 150, 243)",
"rgb(156, 39, 176)",
]
return (
<div
id="tiles"
style={{
"--columns": params().columns,
"--rows": params().rows,
"--bg-color-1": colors[count() % colors.length],
"--bg-color-2": colors[(count() + 1) % colors.length],
}}
>
<For each={Array.from(Array(params().quantity))}>
{(_tile, i) => <Tile index={i()} />}
</For>
</div>
)
} }
export default Grid export default Grid

View file

@ -1,5 +1,5 @@
import classNames from "classnames" import classNames from "classnames"
import { createEffect, createMemo, createSignal } from "solid-js" import { For, createEffect, createSignal } from "solid-js"
function Grid2() { function Grid2() {
function floorClient(number: number) { function floorClient(number: number) {
@ -25,7 +25,7 @@ function Grid2() {
} }
handleResize() handleResize()
window.addEventListener("resize", handleResize) window.addEventListener("resize", handleResize)
}, []) })
createEffect(() => { createEffect(() => {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
@ -36,85 +36,82 @@ function Grid2() {
}) })
}, 500) }, 500)
return () => clearTimeout(timeout) return () => clearTimeout(timeout)
}, [columns, rows]) })
const createTiles = createMemo(() => { const sentences = [
const sentences = [ "Ethem ...",
"Ethem ...", "hat ...",
"hat ...", "lange ...",
"lange ...", "Hörner 🐂",
"Hörner 🐂", "Grüße von Mallorca 🌊 🦦 ☀️",
"Grüße von Mallorca 🌊 🦦 ☀️", ]
]
function createTile(index: number) { function Tile(props: { index: number }) {
const x = index % params().columns const x = () => props.index % params().columns
const y = Math.floor(index / params().columns) const y = () => Math.floor(props.index / params().columns)
const xDiff = (x - position()[0]) / 20 const xDiff = () => (x() - position()[0]) / 20
const yDiff = (y - position()[1]) / 20 const yDiff = () => (y() - position()[1]) / 20
const pos = Math.sqrt(xDiff * xDiff + yDiff * yDiff).toFixed(2) const pos = () =>
Math.sqrt(xDiff() * xDiff() + yDiff() * yDiff()).toFixed(2)
function doEffect(posX: number, posY: number) { function doEffect(posX: number, posY: number) {
if (action()) return if (action()) return
setPosition([posX, posY]) setPosition([posX, posY])
setActve((e) => !e) setActve((e) => !e)
setAction(true) setAction(true)
function xDiff(x: number) { function xDiff(x: number) {
return (x - posX) / 20 return (x - posX) / 20
}
function yDiff(y: number) {
return (y - posY) / 20
}
function pos(x: number, y: number) {
return Math.sqrt(xDiff(x) * xDiff(x) + yDiff(y) * yDiff(y))
}
const diagonals = [
pos(0, 0),
pos(params().columns, 0),
pos(0, params().rows),
pos(params().columns, params().rows),
]
setTimeout(
() => {
setAction(false)
if (active()) setCount((e) => e + 1)
},
Math.max(...diagonals) * 1000 + 1000,
)
} }
function yDiff(y: number) {
return (y - posY) / 20
}
function pos(x: number, y: number) {
return Math.sqrt(xDiff(x) * xDiff(x) + yDiff(y) * yDiff(y))
}
const diagonals = [
pos(0, 0),
pos(params().columns, 0),
pos(0, params().rows),
pos(params().columns, params().rows),
]
return ( setTimeout(
<div () => {
class={classNames("tile", active() ? "active" : "inactive")} setAction(false)
style={{ "--delay": pos + "s" }} if (active()) setCount((e) => e + 1)
onClick={() => doEffect(x, y)} },
/> Math.max(...diagonals) * 1000 + 1000,
) )
} }
return ( return (
<div <div
id="tiles" class={classNames("tile", active() ? "active" : "inactive")}
style={{ style={{ "--delay": pos() + "s" }}
"--columns": params().columns, onClick={() => doEffect(x(), y())}
"--rows": params().rows, />
}}
>
<div class="center-div">
<h1 class={classNames("headline", !active ? "active" : "inactive")}>
{sentences[count() % sentences.length]}
</h1>
</div>
{Array.from(Array(params().quantity), (_tile, index) =>
createTile(index),
)}
</div>
) )
}, [params, position, active, action, count]) }
return createTiles() return (
<div
id="tiles"
style={{
"--columns": params().columns,
"--rows": params().rows,
}}
>
<div class="center-div">
<h1 class={classNames("headline", !active ? "active" : "inactive")}>
{sentences[count() % sentences.length]}
</h1>
</div>
<For each={Array.from(Array(params().quantity))}>
{(_tile, i) => <Tile index={i()} />}
</For>
</div>
)
} }
export default Grid2 export default Grid2

View file

@ -1,14 +1,7 @@
import classNames from "classnames" import classNames from "classnames"
import { JSX } from "solid-js" import { JSX } from "solid-js"
function Button({ function Button(props: {
type,
disabled,
onClick,
children,
latching,
isLatched,
}: {
type: "red" | "orange" | "green" | "gray" type: "red" | "orange" | "green" | "gray"
disabled?: boolean disabled?: boolean
onClick: () => void onClick: () => void
@ -18,26 +11,26 @@ function Button({
}) { }) {
return ( return (
<button <button
disabled={disabled} disabled={props.disabled}
class={classNames( class={classNames(
"font-farro rounded-xl px-8 py-4 text-5xl font-medium duration-100", "font-farro rounded-xl px-8 py-4 text-5xl font-medium duration-100",
disabled props.disabled
? "border-4 border-dashed" ? "border-4 border-dashed"
: latching : props.latching
? isLatched ? props.isLatched
? "mx-1 my-0.5 border-t-4" ? "mx-1 my-0.5 border-t-4"
: "mx-1 my-0.5 border-b-4" : "mx-1 my-0.5 border-b-4"
: "mx-1 my-0.5 border-b-4 active:border-b-0 active:border-t-4", : "mx-1 my-0.5 border-b-4 active:border-b-0 active:border-t-4",
{ {
"border-red-600 bg-red-500": type === "red", "border-red-600 bg-red-500": props.type === "red",
"border-orange-400 bg-warn": type === "orange", "border-orange-400 bg-warn": props.type === "orange",
"border-green-600 bg-green-500": type === "green", "border-green-600 bg-green-500": props.type === "green",
"border-gray-600 bg-gray-500": type === "gray", "border-gray-600 bg-gray-500": props.type === "gray",
}, },
)} )}
onClick={onClick} onClick={() => props.onClick()}
> >
{children} {props.children}
</button> </button>
) )
} }

View file

@ -1,10 +1,6 @@
import { JSX } from "solid-js" import { JSX } from "solid-js"
function Icon({ function Icon(props: {
src,
children,
onClick,
}: {
src: string src: string
children: JSX.Element children: JSX.Element
onClick?: () => void onClick?: () => void
@ -12,14 +8,14 @@ function Icon({
return ( return (
<button <button
class="mx-4 mt-4 flex flex-col items-center border-none" class="mx-4 mt-4 flex flex-col items-center border-none"
onClick={onClick} onClick={() => props.onClick && props.onClick()}
> >
<img <img
class="pixelart mb-1 box-content w-16 rounded-xl bg-white p-1" class="pixelart mb-1 box-content w-16 rounded-xl bg-white p-1"
src={"/assets/" + src} src={"/assets/" + props.src}
alt={src} alt={props.src}
/> />
<span class="font-semibold">{children}</span> <span class="font-semibold">{props.children}</span>
</button> </button>
) )
} }

View file

@ -2,7 +2,7 @@ import {
faRightFromBracket, faRightFromBracket,
faSpinnerThird, faSpinnerThird,
} from "@fortawesome/pro-solid-svg-icons" } from "@fortawesome/pro-solid-svg-icons"
import { JSX, createEffect, createMemo, createSignal } from "solid-js" import { JSX, createEffect, createSignal } from "solid-js"
import { useNavigate } from "solid-start" import { useNavigate } from "solid-start"
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon" import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
import { useGameProps } from "~/hooks/useGameProps" import { useGameProps } from "~/hooks/useGameProps"
@ -14,38 +14,35 @@ import Button from "./Button"
import Icon from "./Icon" import Icon from "./Icon"
import Player from "./Player" import Player from "./Player"
function WithDots({ children }: { children: JSX.Element }) { function WithDots(props: { children: JSX.Element }) {
const [dots, setDots] = createSignal(1) const [dots, setDots] = createSignal(1)
createEffect(() => { createEffect(() => {
const interval = setInterval(() => setDots((e) => (e % 3) + 1), 1000) const interval = setInterval(() => setDots((e) => (e % 3) + 1), 1000)
return () => clearInterval(interval) return () => clearInterval(interval)
}, []) })
return ( return (
<> <>
{children + " "} {props.children + " "}
{Array.from(Array(dots()), () => ".").join("")} {Array.from(Array(dots()), () => ".").join("")}
{Array.from(Array(3 - dots()), (_, i) => ( {Array.from(Array(3 - dots()), () => (
<span>&nbsp;</span> <>&nbsp;</>
))} ))}
</> </>
) )
} }
function LobbyFrame({ openSettings }: { openSettings: () => void }) { function LobbyFrame(props: { openSettings: () => void }) {
const { payload, userStates, full, leave, reset } = useGameProps() const { payload, userStates, full, leave, reset } = useGameProps()
const { isConnected } = useSocket() const { isConnected } = useSocket()
const navigate = useNavigate() const navigate = useNavigate()
const session = useSession() const session = useSession()
const [launchTime, setLaunchTime] = createSignal(3) const [launchTime, setLaunchTime] = createSignal(3)
const launching = createMemo( const launching = () =>
() => payload?.users.length === 2 &&
payload?.users.length === 2 && !userStates.filter((user) => !user.isReady).length
!userStates.filter((user) => !user.isReady).length,
[payload?.users.length, userStates],
)
createEffect(() => { createEffect(() => {
if (!launching() || launchTime() > 0) return if (!launching() || launchTime() > 0) return
@ -66,7 +63,7 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) {
createEffect(() => { createEffect(() => {
if (payload?.game?.id || !isConnected) return if (payload?.game?.id || !isConnected) return
socket.emit("update", full) socket.emit("update", full)
}, [full, payload?.game?.id, isConnected]) })
createEffect(() => { createEffect(() => {
if ( if (
@ -91,7 +88,7 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) {
) : ( ) : (
<> <>
{"Game-PIN: "} {"Game-PIN: "}
{isConnected ? ( {isConnected() ? (
<span class="underline">{payload?.gamePin ?? "----"}</span> <span class="underline">{payload?.gamePin ?? "----"}</span>
) : ( ) : (
<FontAwesomeIcon icon={faSpinnerThird} spin /> <FontAwesomeIcon icon={faSpinnerThird} spin />
@ -99,12 +96,12 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) {
</> </>
)} )}
</h1> </h1>
<Icon src="gear.png" onClick={openSettings}> <Icon src="gear.png" onClick={props.openSettings}>
Settings Settings
</Icon> </Icon>
</div> </div>
<div class="flex items-center justify-around"> <div class="flex items-center justify-around">
{isConnected ? ( {isConnected() ? (
<> <>
<Player <Player
src="player_blue.png" src="player_blue.png"

View file

@ -8,7 +8,7 @@ import {
} from "@fortawesome/pro-solid-svg-icons" } from "@fortawesome/pro-solid-svg-icons"
import { faCaretDown } from "@fortawesome/sharp-solid-svg-icons" import { faCaretDown } from "@fortawesome/sharp-solid-svg-icons"
import classNames from "classnames" import classNames from "classnames"
import { createEffect, createMemo, createSignal } from "solid-js" import { createEffect, createSignal } from "solid-js"
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon" import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
import { useGameProps } from "~/hooks/useGameProps" import { useGameProps } from "~/hooks/useGameProps"
import { socket } from "~/lib/socket" import { socket } from "~/lib/socket"
@ -20,9 +20,9 @@ function HourGlass() {
createEffect(() => { createEffect(() => {
const interval = setInterval(() => setCount((e) => (e + 1) % 4), 1000) const interval = setInterval(() => setCount((e) => (e + 1) % 4), 1000)
return () => clearInterval(interval) return () => clearInterval(interval)
}, []) })
const icon = createMemo(() => { const icon = () => {
switch (count()) { switch (count()) {
case 0: case 0:
return faHourglass3 return faHourglass3
@ -35,26 +35,20 @@ function HourGlass() {
default: default:
return faHourglassClock return faHourglassClock
} }
}, [count]) }
return ( return (
<FontAwesomeIcon icon={icon()} class="ml-4 w-12" spin={count() === 0} /> <FontAwesomeIcon icon={icon()} class="ml-4 w-12" spin={count() === 0} />
) )
} }
function Player({ function Player(props: { src: string; i: number; userId?: string }) {
src,
i,
userId,
}: {
src: string
i: number
userId?: string
}) {
const { payload, userStates, setIsReady } = useGameProps() const { payload, userStates, setIsReady } = useGameProps()
const player = createMemo(() => payload?.users[i], [i, payload?.users]) const player = () => payload?.users[props.i]
const { isReady, isConnected } = userStates[i] const isReady = () => userStates[props.i].isReady
const primary = createMemo(() => userId && userId === payload?.users[i]?.id) const isConnected = () => userStates[props.i].isConnected
const primary = () =>
props.userId && props.userId === payload?.users[props.i]?.id
return ( return (
<div class="flex w-96 flex-col items-center gap-4 p-4"> <div class="flex w-96 flex-col items-center gap-4 p-4">
@ -67,7 +61,11 @@ function Player({
{player?.name ?? "Spieler " + (player()?.index === 2 ? "2" : "1")} {player?.name ?? "Spieler " + (player()?.index === 2 ? "2" : "1")}
</p> </p>
<div class="relative"> <div class="relative">
<img class="pixelart w-64" src={"/assets/" + src} alt={src} /> <img
class="pixelart w-64"
src={"/assets/" + props.src}
alt={props.src}
/>
{primary() ? ( {primary() ? (
<button class="absolute right-4 top-4 h-14 w-14 rounded-lg border-2 border-dashed border-warn bg-gray-800 bg-opacity-90"> <button class="absolute right-4 top-4 h-14 w-14 rounded-lg border-2 border-dashed border-warn bg-gray-800 bg-opacity-90">
<FontAwesomeIcon <FontAwesomeIcon
@ -78,21 +76,21 @@ function Player({
) : null} ) : null}
</div> </div>
<Button <Button
type={isConnected ? (isReady ? "green" : "orange") : "gray"} type={isConnected() ? (isReady() ? "green" : "orange") : "gray"}
latching latching
isLatched={!!isReady} isLatched={!!isReady()}
onClick={() => { onClick={() => {
if (!player) return if (!player()) return
setIsReady({ setIsReady({
i, i: props.i,
isReady: !isReady, isReady: !isReady(),
}) })
socket.emit("isReady", !isReady) socket.emit("isReady", !isReady())
}} }}
disabled={!primary} disabled={!primary()}
> >
Ready Ready
{isReady && isConnected ? ( {isReady() && isConnected() ? (
<FontAwesomeIcon icon={faCheck} class="ml-4 w-12" /> <FontAwesomeIcon icon={faCheck} class="ml-4 w-12" />
) : primary() ? ( ) : primary() ? (
<FontAwesomeIcon <FontAwesomeIcon

View file

@ -3,26 +3,20 @@ import {
faToggleLargeOn, faToggleLargeOn,
} from "@fortawesome/pro-solid-svg-icons" } from "@fortawesome/pro-solid-svg-icons"
import classNames from "classnames" import classNames from "classnames"
import { JSX, createMemo } from "solid-js" import { JSX } from "solid-js"
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon" import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
import { setGameSetting } from "~/components/Gamefield/EventBar" import { setGameSetting } from "~/components/Gamefield/EventBar"
import { useGameProps } from "~/hooks/useGameProps" import { useGameProps } from "~/hooks/useGameProps"
import { GameSettingKeys } from "../../../interfaces/frontend" import { GameSettingKeys } from "../../../interfaces/frontend"
function Setting({ function Setting(props: { children: JSX.Element; key: GameSettingKeys }) {
children,
prop,
}: {
children: JSX.Element
prop: GameSettingKeys
}) {
const { payload, setSetting, full } = useGameProps() const { payload, setSetting, full } = useGameProps()
const state = createMemo(() => payload?.game?.[prop], [payload?.game, prop]) const state = () => payload?.game?.[props.key]
return ( return (
<label class="flex items-center justify-between" for={prop}> <label class="flex items-center justify-between" for={props.key}>
<span class="col-span-2 w-96 select-none text-5xl text-white drop-shadow-md"> <span class="col-span-2 w-96 select-none text-5xl text-white drop-shadow-md">
{children} {props.children}
</span> </span>
<FontAwesomeIcon <FontAwesomeIcon
class={classNames( class={classNames(
@ -39,11 +33,11 @@ function Setting({
class="bg-none" class="bg-none"
checked={state()} checked={state()}
type="checkbox" type="checkbox"
id={prop} id={props.key}
onChange={() => onChange={() =>
setGameSetting( setGameSetting(
{ {
[prop]: !state, [props.key]: !state,
}, },
setSetting, setSetting,
full, full,

View file

@ -7,7 +7,7 @@ import { socket } from "~/lib/socket"
import { GameSettings } from "../../../interfaces/frontend" import { GameSettings } from "../../../interfaces/frontend"
import Setting from "./Setting" import Setting from "./Setting"
function Settings({ closeSettings }: { closeSettings: () => void }) { function Settings(props: { closeSettings: () => void }) {
const { setSetting, full } = useGameProps() const { setSetting, full } = useGameProps()
const gameSetting = (payload: GameSettings) => { const gameSetting = (payload: GameSettings) => {
@ -29,7 +29,7 @@ function Settings({ closeSettings }: { closeSettings: () => void }) {
</h1> </h1>
<button <button
class="right-6 top-6 ml-auto h-14 w-14" class="right-6 top-6 ml-auto h-14 w-14"
onClick={closeSettings} onClick={() => props.closeSettings()}
> >
<FontAwesomeIcon <FontAwesomeIcon
class="h-full w-full text-gray-800 drop-shadow-md" class="h-full w-full text-gray-800 drop-shadow-md"
@ -59,10 +59,10 @@ function Settings({ closeSettings }: { closeSettings: () => void }) {
</button> </button>
</div> </div>
<div class="flex flex-col gap-8"> <div class="flex flex-col gap-8">
<Setting prop="allowSpectators">Erlaube Zuschauer</Setting> <Setting key="allowSpectators">Erlaube Zuschauer</Setting>
<Setting prop="allowSpecials">Erlaube spezial Items</Setting> <Setting key="allowSpecials">Erlaube spezial Items</Setting>
<Setting prop="allowChat">Erlaube den Chat</Setting> <Setting key="allowChat">Erlaube den Chat</Setting>
<Setting prop="allowMarkDraw">Erlaube zeichen/makieren</Setting> <Setting key="allowMarkDraw">Erlaube zeichen/makieren</Setting>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,50 +1,50 @@
import classNames from "classnames" import classNames from "classnames"
function Logo({ small }: { small?: boolean }) { function Logo(props: { small?: boolean }) {
return ( return (
<div class="relative flex flex-col items-center rounded-sm border-x-4 border-y-2 border-shield-gray bg-shield-lightgray md:border-x-8 md:border-y-4"> <div class="relative flex flex-col items-center rounded-sm border-x-4 border-y-2 border-shield-gray bg-shield-lightgray md:border-x-8 md:border-y-4">
<h1 <h1
class={classNames( class={classNames(
"font-checkpoint mx-16 my-2 flex flex-col gap-2 border-y-2 border-slate-700 text-center text-2xl leading-tight tracking-widest sm:mx-24 sm:my-3 sm:gap-3 sm:border-y-[3px] sm:text-4xl md:mx-36 md:my-4 md:gap-4 md:border-y-4 md:text-5xl", "font-checkpoint mx-16 my-2 flex flex-col gap-2 border-y-2 border-slate-700 text-center text-2xl leading-tight tracking-widest sm:mx-24 sm:my-3 sm:gap-3 sm:border-y-[3px] sm:text-4xl md:mx-36 md:my-4 md:gap-4 md:border-y-4 md:text-5xl",
{ "xl:gap-6 xl:py-2 xl:text-6xl": !small }, { "xl:gap-6 xl:py-2 xl:text-6xl": !props.small },
)} )}
> >
<span>Leaky</span> <span>Leaky</span>
<span>Ships</span> <span>Ships</span>
</h1> </h1>
<Screws small={small} /> <Screws small={props.small} />
</div> </div>
) )
} }
function Screws({ small }: { small?: boolean }) { function Screws(props: { small?: boolean }) {
return ( return (
<> <>
<Screw <Screw
small={small} small={props.small}
orientation={classNames("top-1 left-1 sm:top-2 sm:left-2", { orientation={classNames("top-1 left-1 sm:top-2 sm:left-2", {
"xl:top-4 xl:left-4": !small, "xl:top-4 xl:left-4": !props.small,
})} })}
rotation="rotate-[135deg]" rotation="rotate-[135deg]"
/> />
<Screw <Screw
small={small} small={props.small}
orientation={classNames("top-1 right-1 sm:top-2 sm:right-2", { orientation={classNames("top-1 right-1 sm:top-2 sm:right-2", {
"xl:top-4 xl:right-4": !small, "xl:top-4 xl:right-4": !props.small,
})} })}
rotation="rotate-[5deg]" rotation="rotate-[5deg]"
/> />
<Screw <Screw
small={small} small={props.small}
orientation={classNames("bottom-1 right-1 sm:bottom-2 sm:right-2", { orientation={classNames("bottom-1 right-1 sm:bottom-2 sm:right-2", {
"xl:bottom-4 xl:right-4": !small, "xl:bottom-4 xl:right-4": !props.small,
})} })}
rotation="rotate-[150deg]" rotation="rotate-[150deg]"
/> />
<Screw <Screw
small={small} small={props.small}
orientation={classNames("bottom-1 left-1 sm:bottom-2 sm:left-2", { orientation={classNames("bottom-1 left-1 sm:bottom-2 sm:left-2", {
"xl:bottom-4 xl:left-4": !small, "xl:bottom-4 xl:left-4": !props.small,
})} })}
rotation="rotate-[20deg]" rotation="rotate-[20deg]"
/> />
@ -52,11 +52,7 @@ function Screws({ small }: { small?: boolean }) {
) )
} }
function Screw({ function Screw(props: {
orientation,
rotation,
small,
}: {
orientation: string orientation: string
rotation: string rotation: string
small?: boolean small?: boolean
@ -65,15 +61,15 @@ function Screw({
<div <div
class={classNames( class={classNames(
"absolute flex h-3 w-3 flex-col items-center justify-center rounded-full border-[1px] border-neutral-700 bg-neutral-400 sm:h-5 sm:w-5 sm:border-2 md:h-6 md:w-6", "absolute flex h-3 w-3 flex-col items-center justify-center rounded-full border-[1px] border-neutral-700 bg-neutral-400 sm:h-5 sm:w-5 sm:border-2 md:h-6 md:w-6",
{ "xl:h-8 xl:w-8": !small }, { "xl:h-8 xl:w-8": !props.small },
orientation, props.orientation,
)} )}
> >
<hr <hr
class={classNames( class={classNames(
"color w-full border-neutral-500 sm:border-t-2", "color w-full border-neutral-500 sm:border-t-2",
{ "xl:border-t-4": !small }, { "xl:border-t-4": !props.small },
rotation, props.rotation,
)} )}
/> />
</div> </div>

View file

@ -1,37 +1,36 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core" import { IconDefinition } from "@fortawesome/fontawesome-svg-core"
import classNames from "classnames" import classNames from "classnames"
import { JSX } from "solid-js" import { JSX, Show } from "solid-js"
import { FontAwesomeIcon } from "./FontAwesomeIcon" import { FontAwesomeIcon } from "./FontAwesomeIcon"
function OptionButton({ function OptionButton(props: {
id, text: string
icon,
callback,
node,
disabled,
}: {
id: string
icon: IconDefinition icon: IconDefinition
callback?: () => void callback?: () => void
node?: JSX.Element node?: JSX.Element
nodeWhen?: boolean
disabled?: boolean disabled?: boolean
}) { }) {
return ( return (
<button <button
class={classNames( class={classNames(
"flex w-full flex-row items-center justify-between rounded-xl py-2 pl-8 pr-4 text-lg text-grayish duration-100 first:mt-4 last:mt-4 sm:py-4 sm:pl-16 sm:pr-8 sm:text-4xl sm:first:mt-8 sm:last:mt-8", "flex w-full flex-row items-center justify-between rounded-xl py-2 pl-8 pr-4 text-lg text-grayish duration-100 first:mt-4 last:mt-4 sm:py-4 sm:pl-16 sm:pr-8 sm:text-4xl sm:first:mt-8 sm:last:mt-8",
!disabled !props.disabled
? "border-b-4 border-shield-gray bg-voidDark active:border-b-0 active:border-t-4" ? "border-b-4 border-shield-gray bg-voidDark active:border-b-0 active:border-t-4"
: "border-4 border-dashed border-slate-600 bg-red-950", : "border-4 border-dashed border-slate-600 bg-red-950",
)} )}
onClick={() => callback && setTimeout(callback, 200)} onClick={() => props.callback && setTimeout(props.callback, 200)}
disabled={disabled} disabled={props.disabled}
title={!disabled ? "" : "Please login"} title={!props.disabled ? "" : "Please login"}
> >
<span class="mx-auto">{node ? node : id}</span> <Show
when={props.nodeWhen}
fallback={<span class="mx-auto">{props.text}</span>}
children={props.node}
/>
<FontAwesomeIcon <FontAwesomeIcon
class="ml-2 w-10 text-xl sm:ml-12 sm:text-4xl" class="ml-2 w-10 text-xl sm:ml-12 sm:text-4xl"
icon={icon} icon={props.icon}
/> />
</button> </button>
) )

View file

@ -8,7 +8,7 @@ function drawLine({ prevPoint, currentPoint, ctx, color }: Draw) {
const lineColor = color const lineColor = color
const lineWidth = 5 const lineWidth = 5
let startPoint = prevPoint ?? currentPoint const startPoint = prevPoint ?? currentPoint
ctx.beginPath() ctx.beginPath()
ctx.lineWidth = lineWidth ctx.lineWidth = lineWidth
ctx.strokeStyle = lineColor ctx.strokeStyle = lineColor
@ -79,7 +79,7 @@ export const useDraw = () => {
canvas.removeEventListener("mousemove", handler) canvas.removeEventListener("mousemove", handler)
window.removeEventListener("mouseup", mouseUpHandler) window.removeEventListener("mouseup", mouseUpHandler)
} }
}, [color, mouseDown]) })
createEffect(() => { createEffect(() => {
const canvas = canvasRef const canvas = canvasRef
@ -94,7 +94,7 @@ export const useDraw = () => {
socket.emit("canvas-state", canvasRef.toDataURL()) socket.emit("canvas-state", canvasRef.toDataURL())
} }
const canvasStateFromServer = (state: string, index: number) => { const canvasStateFromServer = (state: string) => {
console.log("I received the state") console.log("I received the state")
const img = new Image() const img = new Image()
img.src = state img.src = state
@ -103,10 +103,9 @@ export const useDraw = () => {
} }
} }
const socketDrawLine = (props: DrawLineProps, index: number) => { const socketDrawLine = (props: DrawLineProps) => {
const { prevPoint, currentPoint, color } = props
if (!ctx) return console.log("no ctx here") if (!ctx) return console.log("no ctx here")
drawLine({ prevPoint, currentPoint, ctx, color }) drawLine({ ctx, ...props })
} }
socket.on("playerEvent", playerEvent) socket.on("playerEvent", playerEvent)

View file

@ -1,5 +1,4 @@
import { GameState, MoveType } from "@prisma/client" import { GameState, MoveType } from "@prisma/client"
import { produce } from "immer"
import { getPayloadwithChecksum } from "~/lib/getPayloadwithChecksum" import { getPayloadwithChecksum } from "~/lib/getPayloadwithChecksum"
import { socket } from "~/lib/socket" import { socket } from "~/lib/socket"
import { import {
@ -17,6 +16,7 @@ import {
// import { toast } from "react-toastify" // import { toast } from "react-toastify"
import create from "solid-zustand" import create from "solid-zustand"
import { Accessor, Setter, createSignal } from "solid-js"
import { import {
EventBarModes, EventBarModes,
GameSettings, GameSettings,
@ -29,8 +29,10 @@ import {
const initialState: optionalGamePropsSchema & { const initialState: optionalGamePropsSchema & {
userStates: { userStates: {
isReady: boolean isReady: Accessor<boolean>
isConnected: boolean setIsReady: Setter<boolean>
isConnected: Accessor<boolean>
setIsConnected: Setter<boolean>
}[] }[]
menu: keyof EventBarModes menu: keyof EventBarModes
mode: number mode: number
@ -45,10 +47,11 @@ const initialState: optionalGamePropsSchema & {
target: initlialTarget, target: initlialTarget,
targetPreview: initlialTargetPreview, targetPreview: initlialTargetPreview,
mouseCursor: initlialMouseCursor, mouseCursor: initlialMouseCursor,
userStates: Array.from(Array(2), () => ({ userStates: Array.from(Array(2), () => {
isReady: false, const [isReady, setIsReady] = createSignal(false)
isConnected: false, const [isConnected, setIsConnected] = createSignal(false)
})), return { isReady, setIsReady, isConnected, setIsConnected }
}),
} }
export type State = typeof initialState export type State = typeof initialState
@ -78,137 +81,129 @@ export type Action = {
export const useGameProps = create<State & Action>()((set) => ({ export const useGameProps = create<State & Action>()((set) => ({
...initialState, ...initialState,
setActiveIndex: (i, selfIndex) => setActiveIndex: (i, selfIndex) =>
set( set((state: State) => {
produce((state: State) => { if (!state.payload) return state
if (!state.payload) return state.payload.activeIndex = i
state.payload.activeIndex = i if (i === selfIndex) {
if (i === selfIndex) { state.menu = "moves"
state.menu = "moves" state.mode = 0
state.mode = 0 } else {
} else { state.menu = "main"
state.menu = "main" state.mode = -1
state.mode = -1 }
} return state
}), }),
),
DispatchMove: (move, i) => DispatchMove: (move, i) =>
set( set((state: State) => {
produce((state: State) => { if (!state.payload) return state
if (!state.payload) return const list = targetList(move, move.type)
const list = targetList(move, move.type) state.payload.users.map((e) => {
state.payload.users.map((e) => { if (!e) return e
if (!e) return e if (i === e.index) e.moves.push(move)
if (i === e.index) e.moves.push(move) else if (move.type !== MoveType.radar)
else if (move.type !== MoveType.radar) e.hits.push(
e.hits.push( ...list.map(({ x, y }) => ({
...list.map(({ x, y }) => ({ hit: !!intersectingShip(e.ships, {
hit: !!intersectingShip(e.ships, { ...move,
...move, size: 1,
size: 1, variant: 0,
variant: 0, }).fields.length,
}).fields.length, x,
x, y,
y, })),
})),
)
return e
})
}),
),
setTarget: (dispatch) =>
set(
produce((state: State) => {
if (typeof dispatch === "function")
state.target = dispatch(state.target)
else state.target = dispatch
}),
),
setTargetPreview: (dispatch) =>
set(
produce((state: State) => {
if (typeof dispatch === "function")
state.targetPreview = dispatch(state.targetPreview)
else state.targetPreview = dispatch
}),
),
setMouseCursor: (dispatch) =>
set(
produce((state: State) => {
if (typeof dispatch === "function")
state.mouseCursor = dispatch(state.mouseCursor)
else state.mouseCursor = dispatch
}),
),
setShips: (ships, index) =>
set(
produce((state: State) => {
if (!state.payload) return
state.payload.users = state.payload.users.map((e) => {
if (!e || e.index !== index) return e
e.ships = ships
return e
})
}),
),
removeShip: ({ size, variant, x, y }, index) =>
set(
produce((state: State) => {
state.payload?.users.map((e) => {
if (!e || e.index !== index) return
const indexToRemove = e.ships.findIndex(
(ship) =>
ship.size === size &&
ship.variant === variant &&
ship.x === x &&
ship.y === y,
) )
e.ships.splice(indexToRemove, 1)
return e return e
}) })
}), return state
), }),
setTarget: (dispatch) =>
set((state: State) => {
if (typeof dispatch === "function") state.target = dispatch(state.target)
else state.target = dispatch
return state
}),
setTargetPreview: (dispatch) =>
set((state: State) => {
if (typeof dispatch === "function")
state.targetPreview = dispatch(state.targetPreview)
else state.targetPreview = dispatch
return state
}),
setMouseCursor: (dispatch) =>
set((state: State) => {
if (typeof dispatch === "function")
state.mouseCursor = dispatch(state.mouseCursor)
else state.mouseCursor = dispatch
return state
}),
setShips: (ships, index) =>
set((state: State) => {
if (!state.payload) return state
state.payload.users = state.payload.users.map((e) => {
if (!e || e.index !== index) return e
e.ships = ships
return e
})
return state
}),
removeShip: ({ size, variant, x, y }, index) =>
set((state: State) => {
state.payload?.users.map((e) => {
if (!e || e.index !== index) return
const indexToRemove = e.ships.findIndex(
(ship) =>
ship.size === size &&
ship.variant === variant &&
ship.x === x &&
ship.y === y,
)
e.ships.splice(indexToRemove, 1)
return e
})
return state
}),
setPlayer: (payload) => { setPlayer: (payload) => {
let hash: string | null = null let hash: string | null = null
set( set((state: State) => {
produce((state: State) => { if (!state.payload) return state
if (!state.payload) return state.payload.users = payload.users
state.payload.users = payload.users const body = getPayloadwithChecksum(state.payload)
const body = getPayloadwithChecksum(state.payload) if (!body.hash) {
if (!body.hash) { // toast.warn("Something is wrong... ", {
// toast.warn("Something is wrong... ", { // toastId: "st_wrong",
// toastId: "st_wrong", // theme: "colored",
// theme: "colored", // })
// })
return return state
} }
hash = body.hash hash = body.hash
state.hash = hash state.hash = hash
}), return state
) })
return hash return hash
}, },
setSetting: (settings) => { setSetting: (settings) => {
let hash: string | null = null let hash: string | null = null
set( set((state: State) => {
produce((state: State) => { if (!state.payload?.game) return state
if (!state.payload?.game) return Object.assign(state.payload.game, settings)
Object.assign(state.payload.game, settings) const body = getPayloadwithChecksum(state.payload)
const body = getPayloadwithChecksum(state.payload) if (!body.hash) {
if (!body.hash) { // toast.warn("Something is wrong... ", {
// toast.warn("Something is wrong... ", { // toastId: "st_wrong",
// toastId: "st_wrong", // theme: "colored",
// theme: "colored", // })
// }) return state
return }
} hash = body.hash
hash = body.hash state.hash = hash
state.hash = hash return state
}), })
)
return hash return hash
}, },
full: (newGameProps) => full: (newGameProps) =>
// eslint-disable-next-line solid/reactivity
set((state) => { set((state) => {
if (state.hash === newGameProps.hash) { if (state.hash === newGameProps.hash) {
console.log("Everything up to date.") console.log("Everything up to date.")
@ -239,31 +234,26 @@ export const useGameProps = create<State & Action>()((set) => ({
}) })
}, },
setIsReady: ({ i, isReady }) => setIsReady: ({ i, isReady }) =>
set( set((state: State) => {
produce((state: State) => { state.userStates[i].setIsReady(isReady)
state.userStates[i].isReady = isReady state.userStates[i].setIsConnected(true)
state.userStates[i].isConnected = true return state
}), }),
),
gameState: (newState: GameState) => gameState: (newState: GameState) =>
set( set((state: State) => {
produce((state: State) => { if (!state.payload?.game) return state
if (!state.payload?.game) return state.payload.game.state = newState
state.payload.game.state = newState state.userStates.forEach((e) => {
state.userStates = state.userStates.map((e) => ({ e.setIsReady(false)
...e, })
isReady: false, return state
})) }),
}),
),
setIsConnected: ({ i, isConnected }) => setIsConnected: ({ i, isConnected }) =>
set( set((state: State) => {
produce((state: State) => { state.userStates[i].setIsConnected(isConnected)
state.userStates[i].isConnected = isConnected if (!isConnected) state.userStates[i].setIsReady(false)
if (isConnected) return return state
state.userStates[i].isReady = false }),
}),
),
reset: () => { reset: () => {
set(initialState) set(initialState)
}, },

View file

@ -1,4 +1,3 @@
import { createMemo } from "solid-js"
import { ShipProps } from "../interfaces/frontend" import { ShipProps } from "../interfaces/frontend"
import { useGameProps } from "./useGameProps" import { useGameProps } from "./useGameProps"
import useIndex from "./useIndex" import useIndex from "./useIndex"
@ -7,10 +6,8 @@ function useShips() {
const gameProps = useGameProps() const gameProps = useGameProps()
const { selfIndex } = useIndex() const { selfIndex } = useIndex()
const ships = createMemo( const ships = () =>
() => gameProps.payload?.users.find((e) => e?.index === selfIndex)?.ships ?? []
gameProps.payload?.users.find((e) => e?.index === selfIndex)?.ships ?? [],
)
const setShips = (ships: ShipProps[]) => gameProps.setShips(ships, selfIndex) const setShips = (ships: ShipProps[]) => gameProps.setShips(ships, selfIndex)
const removeShip = (ship: ShipProps) => gameProps.removeShip(ship, selfIndex) const removeShip = (ship: ShipProps) => gameProps.removeShip(ship, selfIndex)

View file

@ -1,6 +1,6 @@
import status from "http-status" import status from "http-status"
// import { toast } from "react-toastify" // import { toast } from "react-toastify"
import { createEffect, createMemo, createSignal } from "solid-js" import { createEffect, createSignal } from "solid-js"
import { useNavigate } from "solid-start" import { useNavigate } from "solid-start"
import { socket } from "~/lib/socket" import { socket } from "~/lib/socket"
import { GamePropsSchema } from "~/lib/zodSchemas" import { GamePropsSchema } from "~/lib/zodSchemas"
@ -28,11 +28,8 @@ function useSocket() {
} = useGameProps() } = useGameProps()
const navigate = useNavigate() const navigate = useNavigate()
const isConnected = createMemo( const isConnected = () =>
() => selfIndex >= 0 ? userStates[selfIndex].isConnected() : isConnectedState()
selfIndex >= 0 ? userStates[selfIndex].isConnected : isConnectedState(),
[selfIndex, isConnectedState(), userStates],
)
createEffect(() => { createEffect(() => {
if (selfIndex < 0) return if (selfIndex < 0) return
@ -40,7 +37,7 @@ function useSocket() {
i: selfIndex, i: selfIndex,
isConnected: isConnectedState(), isConnected: isConnectedState(),
}) })
}, [selfIndex, isConnectedState(), setIsConnected]) })
createEffect(() => { createEffect(() => {
const connect = () => { const connect = () => {
@ -66,7 +63,7 @@ function useSocket() {
const playerEvent = (event: PlayerEvent) => { const playerEvent = (event: PlayerEvent) => {
const { type, i } = event const { type, i } = event
let message: string // let message: string
console.log("playerEvent", type) console.log("playerEvent", type)
switch (type) { switch (type) {
case "disconnect": case "disconnect":
@ -74,11 +71,11 @@ function useSocket() {
i, i,
isConnected: false, isConnected: false,
}) })
message = "Player is disconnected." // message = "Player is disconnected."
break break
case "leave": case "leave":
message = "Player has left the lobby." // message = "Player has left the lobby."
break break
case "connect": case "connect":
@ -86,12 +83,12 @@ function useSocket() {
i, i,
isConnected: true, isConnected: true,
}) })
socket.emit("isReady", userStates[selfIndex].isReady) socket.emit("isReady", userStates[selfIndex].isReady())
message = "Player has joined the lobby." // message = "Player has joined the lobby."
break break
default: default:
message = "Not defined yet." // message = "Not defined yet."
break break
} }
// toast.info(message, { toastId: message }) // toast.info(message, { toastId: message })
@ -172,7 +169,7 @@ function useSocket() {
return { return {
isConnected: isConnected:
selfIndex >= 0 ? userStates[selfIndex].isConnected : isConnectedState(), selfIndex >= 0 ? userStates[selfIndex].isConnected : isConnectedState,
} }
} }

View file

@ -23,7 +23,7 @@ export interface TargetList extends Position {
edges: string[] edges: string[]
} }
export interface Mode { export interface Mode {
pointerGrid: any[][] pointerGrid: void[][]
type: MoveType type: MoveType
} }
export interface ItemProps { export interface ItemProps {

View file

@ -2,7 +2,7 @@ import { APIEvent, json } from "solid-start"
import { rejectionError } from "./errors" import { rejectionError } from "./errors"
import logging from "./logging" import logging from "./logging"
export default function sendError<T>( export default function sendError(
request: APIEvent["request"], request: APIEvent["request"],
err: rejectionError | Error, err: rejectionError | Error,
) { ) {

View file

@ -153,8 +153,8 @@ export const shipProps = (
orientation: targetPreview.orientation, orientation: targetPreview.orientation,
}) })
export function shipFields(ship: ShipProps, i?: number) { export function shipFields(ship: ShipProps, i?: number) {
let fields: IndexedPosition[] = [] const fields: IndexedPosition[] = []
let borders: IndexedPosition[] = [] const borders: IndexedPosition[] = []
for ( for (
let x = ship.x; let x = ship.x;
x <= (ship.orientation === "h" ? ship.x + ship.size - 1 : ship.x); x <= (ship.orientation === "h" ? ship.x + ship.size - 1 : ship.x);

View file

@ -11,6 +11,7 @@ interface Data {
} }
export async function GET({ request }: APIEvent) { export async function GET({ request }: APIEvent) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const body = request.json() as any //TODO const body = request.json() as any //TODO
const gameId = body.query.id const gameId = body.query.id
const session = await getSession(request, authOptions) const session = await getSession(request, authOptions)
@ -39,6 +40,6 @@ export async function GET({ request }: APIEvent) {
sendResponse(request, { sendResponse(request, {
message: "Here is the game.", message: "Here is the game.",
body: { game }, body: { game } as Data,
}) })
} }

View file

@ -19,7 +19,7 @@ export async function POST({ request }: APIEvent) {
.toString() .toString()
.padStart(4, "0") .padStart(4, "0")
let created = false const created = false
let game = await getAnyRunningGame(id) let game = await getAnyRunningGame(id)
if (game) { if (game) {

View file

@ -74,6 +74,7 @@ export async function POST({ request }: APIEvent) {
body, body,
type: ["debug", "infoCyan"], type: ["debug", "infoCyan"],
}) })
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) { } catch (err: any) {
await logging( await logging(
"HERE".red + err.code + err.meta + err.message, "HERE".red + err.code + err.meta + err.message,

View file

@ -33,7 +33,7 @@ export async function GET({ request }: { request: RequestWithSocket }) {
// io.use(authenticate) // io.use(authenticate)
io.use(async (socket, next) => { io.use(async (socket, next) => {
try { try {
// @ts-ignore // @ts-expect-error TODO add correct server
const session = await getSession(socket.request, authOptions) const session = await getSession(socket.request, authOptions)
if (!session) return next(new Error(status["401"])) if (!session) return next(new Error(status["401"]))
socket.data.user = session.user socket.data.user = session.user
@ -66,7 +66,7 @@ export async function GET({ request }: { request: RequestWithSocket }) {
}) })
next() next()
} catch (err: any) { } catch {
logging("Unkonwn error - " + status["401"], ["warn"], socket.request) logging("Unkonwn error - " + status["401"], ["warn"], socket.request)
next(new Error(status["401"])) next(new Error(status["401"]))
} }

View file

@ -1,40 +1,40 @@
// import { toast } from "react-toastify" // import { toast } from "react-toastify"
import { createEffect } from "solid-js" // import { createEffect } from "solid-js"
import { useNavigate } from "solid-start" // import { useNavigate } from "solid-start"
import { useGameProps } from "~/hooks/useGameProps" // import { useGameProps } from "~/hooks/useGameProps"
import { useSession } from "~/hooks/useSession" // import { useSession } from "~/hooks/useSession"
export default function Game() { export default function Game() {
const { payload } = useGameProps() // const { payload } = useGameProps()
const navigate = useNavigate() // const navigate = useNavigate()
const session = useSession() // const session = useSession()
createEffect(() => { // createEffect(() => {
const gameId = payload?.game?.id // const gameId = payload?.game?.id
const path = gameId ? "/game" : "/start" // const path = gameId ? "/game" : "/start"
// toast.promise(navigate(path), { // toast.promise(navigate(path), {
// pending: { // pending: {
// render: "Wird weitergeleitet...", // render: "Wird weitergeleitet...",
// toastId: "pageLoad", // toastId: "pageLoad",
// }, // },
// success: { // success: {
// render: gameId // render: gameId
// ? "Spiel gefunden!" // ? "Spiel gefunden!"
// : session?.user.id // : session?.user.id
// ? "Kein laufendes Spiel." // ? "Kein laufendes Spiel."
// : "Kein laufendes Spiel. Bitte anmelden.", // : "Kein laufendes Spiel. Bitte anmelden.",
// toastId: "pageLoad", // toastId: "pageLoad",
// theme: session?.user.id ? "dark" : undefined, // theme: session?.user.id ? "dark" : undefined,
// type: gameId ? "success" : "info", // type: gameId ? "success" : "info",
// }, // },
// error: { // error: {
// render: "Es ist ein Fehler aufgetreten 🤯", // render: "Es ist ein Fehler aufgetreten 🤯",
// type: "error", // type: "error",
// toastId: "pageLoad", // toastId: "pageLoad",
// theme: "colored", // theme: "colored",
// }, // },
// }) // })
}) // })
return ( return (
<div class="h-full bg-theme"> <div class="h-full bg-theme">

View file

@ -4,7 +4,7 @@ import { GamePropsSchema } from "~/lib/zodSchemas"
// import OtpInput from "react-otp-input" // import OtpInput from "react-otp-input"
// import { Icons, toast } from "react-toastify" // import { Icons, toast } from "react-toastify"
import status from "http-status" import status from "http-status"
import { createEffect, createMemo, createSignal } from "solid-js" import { createEffect, createSignal } from "solid-js"
import { useLocation, useNavigate, useSearchParams } from "solid-start" import { useLocation, useNavigate, useSearchParams } from "solid-start"
import BurgerMenu from "~/components/BurgerMenu" import BurgerMenu from "~/components/BurgerMenu"
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon" import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
@ -30,28 +30,28 @@ export function isAuthenticated(res: Response) {
// }) // })
} }
const handleConfirmation = () => { // const handleConfirmation = () => {
const toastId = "confirm" // const toastId = "confirm"
// toast.warn( // toast.warn(
// <div id="toast-confirm"> // <div id="toast-confirm">
// <h4>You are already in another round, do you want to:</h4> // <h4>You are already in another round, do you want to:</h4>
// <button onClick={() => toast.dismiss(toastId)}>Join</button> // <button onClick={() => toast.dismiss(toastId)}>Join</button>
// or // or
// <button onClick={() => toast.dismiss(toastId)}>Leave</button> // <button onClick={() => toast.dismiss(toastId)}>Leave</button>
// </div>, // </div>,
// { autoClose: false, toastId }, // { autoClose: false, toastId },
// ) // )
} // }
export default function Start() { export default function Start() {
const [otp, setOtp] = createSignal("") const [otp] = createSignal("")
const gameProps = useGameProps() const gameProps = useGameProps()
const location = useLocation() const location = useLocation()
const navigate = useNavigate() const navigate = useNavigate()
const session = useSession() const session = useSession()
const [searchParams] = useSearchParams() const [searchParams] = useSearchParams()
const query = createMemo(() => { const query = () => {
switch (searchParams["q"]) { switch (searchParams["q"]) {
case "join": case "join":
return { join: true } return { join: true }
@ -60,7 +60,7 @@ export default function Start() {
default: default:
return {} return {}
} }
}) }
const gameFetch = async (pin?: string) => { const gameFetch = async (pin?: string) => {
const gameRequestPromise = fetch( const gameRequestPromise = fetch(
@ -73,8 +73,8 @@ export default function Start() {
.then(isAuthenticated) .then(isAuthenticated)
.then((game) => GamePropsSchema.parse(game)) .then((game) => GamePropsSchema.parse(game))
const move = !pin ? "erstellt" : "angefragt" // const move = !pin ? "erstellt" : "angefragt"
const toastId = "pageLoad" // const toastId = "pageLoad"
// toast("Raum wird " + move, { // toast("Raum wird " + move, {
// icon: Icons.spinner(), // icon: Icons.spinner(),
// toastId, // toastId,
@ -163,13 +163,13 @@ export default function Start() {
</div> </div>
<div class="flex flex-col items-center gap-6 sm:gap-12"> <div class="flex flex-col items-center gap-6 sm:gap-12">
<OptionButton <OptionButton
id="Raum erstellen" text="Raum erstellen"
callback={gameFetch} callback={gameFetch}
icon={faPlus} icon={faPlus}
disabled={!session.latest} disabled={!session.latest}
/> />
<OptionButton <OptionButton
id="Raum beitreten" text="Raum beitreten"
callback={() => callback={() =>
navigate( navigate(
location.pathname.concat( location.pathname.concat(
@ -180,9 +180,11 @@ export default function Start() {
} }
icon={faUserPlus} icon={faUserPlus}
disabled={!session.latest} disabled={!session.latest}
nodeWhen={query().join && !!session.latest}
node={ node={
query().join && session.latest <>
? // <OtpInput {
// <OtpInput
// shouldAutoFocus // shouldAutoFocus
// containerStyle={{ color: "initial" }} // containerStyle={{ color: "initial" }}
// value={otp} // value={otp}
@ -194,24 +196,27 @@ export default function Start() {
// renderSeparator={<span>-</span>} // renderSeparator={<span>-</span>}
// renderInput={(props) => <input {...props} />} // renderInput={(props) => <input {...props} />}
// /> // />
null }
: null </>
} }
/> />
<OptionButton <OptionButton
id="Zuschauen" text="Zuschauen"
icon={faEye} icon={faEye}
callback={() => callback={() =>
navigate( navigate(
location.pathname.concat( location.pathname +
"?", "?" +
new URLSearchParams({ q: "watch" }).toString(), new URLSearchParams({
), q: "watch",
}).toString(),
) )
} }
nodeWhen={query().watch}
node={ node={
query().watch <>
? // <OtpInput {
// <OtpInput
// shouldAutoFocus // shouldAutoFocus
// containerStyle={{ color: "initial" }} // containerStyle={{ color: "initial" }}
// value={otp} // value={otp}
@ -223,8 +228,8 @@ export default function Start() {
// renderSeparator={<span>-</span>} // renderSeparator={<span>-</span>}
// renderInput={(props) => <input {...props} />} // renderInput={(props) => <input {...props} />}
// /> // />
null }
: null </>
} }
/> />
</div> </div>