Replace gameProp signals with store

This commit is contained in:
aronmal 2023-09-08 18:55:59 +02:00
parent 4390269ed1
commit 252f6f6028
Signed by: aronmal
GPG key ID: 816B7707426FC612
14 changed files with 331 additions and 339 deletions

View file

@ -1,10 +1,9 @@
import { For } from "solid-js" import { For } from "solid-js"
import { import {
gameState, gameProps,
mode,
mouseCursor, mouseCursor,
removeShip, removeShip,
setMode, setGameProps,
setMouseCursor, setMouseCursor,
setShips, setShips,
setTarget, setTarget,
@ -13,6 +12,7 @@ import {
import { useSession } from "~/hooks/useSession" import { useSession } from "~/hooks/useSession"
import { import {
borderCN, borderCN,
compiledHits,
cornerCN, cornerCN,
fieldIndex, fieldIndex,
intersectingShip, intersectingShip,
@ -32,19 +32,21 @@ type TilesType = {
} }
function BorderTiles() { function BorderTiles() {
const { selfIndex, activeUser, ships } = useSession() const { selfIndex, ships } = useSession()
const settingTarget = (isGameTile: boolean, x: number, y: number) => { const settingTarget = (isGameTile: boolean, x: number, y: number) => {
if (gameState() === "running") { const sIndex = selfIndex()
const list = targetList(targetPreview(), mode()) if (sIndex === -1) return
if (gameProps.gameState === "running") {
const list = targetList(targetPreview(), gameProps.mode)
if ( if (
!isGameTile || !isGameTile ||
!list.filter( !list.filter(
({ x, y }) => !isAlreadyHit(x, y, activeUser()?.hits() ?? []), ({ x, y }) => !isAlreadyHit(x, y, compiledHits(sIndex === 0 ? 1 : 0)),
).length ).length
) )
return return
if (!overlapsWithAnyBorder(targetPreview(), mode())) if (!overlapsWithAnyBorder(targetPreview(), gameProps.mode))
setTarget({ setTarget({
show: true, show: true,
x, x,
@ -52,15 +54,17 @@ function BorderTiles() {
orientation: targetPreview().orientation, orientation: targetPreview().orientation,
}) })
} else if ( } else if (
gameState() === "starting" && gameProps.gameState === "starting" &&
targetPreview().show && targetPreview().show &&
!intersectingShip(ships(), shipProps(ships(), mode(), targetPreview())) !intersectingShip(
.score ships(),
shipProps(ships(), gameProps.mode, targetPreview()),
).score
) { ) {
setMouseCursor((e) => ({ ...e, shouldShow: false })) setMouseCursor((e) => ({ ...e, shouldShow: false }))
setShips( setShips(
[...ships(), shipProps(ships(), mode(), targetPreview())], [...ships(), shipProps(ships(), gameProps.mode, targetPreview())],
selfIndex(), sIndex,
) )
} }
} }
@ -94,9 +98,11 @@ function BorderTiles() {
class={props.className} class={props.className}
style={{ "--x": props.x, "--y": props.y }} style={{ "--x": props.x, "--y": props.y }}
onClick={() => { onClick={() => {
if (gameState() === "running") { const sIndex = selfIndex()
if (sIndex === -1) return
if (gameProps.gameState === "running") {
settingTarget(props.isGameTile, props.x, props.y) settingTarget(props.isGameTile, props.x, props.y)
} else if (gameState() === "starting") { } else if (gameProps.gameState === "starting") {
const { index } = intersectingShip(ships(), { const { index } = intersectingShip(ships(), {
...mouseCursor(), ...mouseCursor(),
size: 1, size: 1,
@ -107,8 +113,8 @@ function BorderTiles() {
settingTarget(props.isGameTile, props.x, props.y) settingTarget(props.isGameTile, props.x, props.y)
else { else {
const ship = ships()[index] const ship = ships()[index]
setMode(ship.size - 2) setGameProps("mode", ship.size - 2)
removeShip(ship, selfIndex()) removeShip(ship, sIndex)
setMouseCursor((e) => ({ ...e, shouldShow: true })) setMouseCursor((e) => ({ ...e, shouldShow: true }))
} }
} }
@ -119,10 +125,10 @@ function BorderTiles() {
y: props.y, y: props.y,
shouldShow: shouldShow:
props.isGameTile && props.isGameTile &&
(gameState() === "starting" (gameProps.gameState === "starting"
? intersectingShip( ? intersectingShip(
ships(), ships(),
shipProps(ships(), mode(), { shipProps(ships(), gameProps.mode, {
x: props.x, x: props.x,
y: props.y, y: props.y,
orientation: targetPreview().orientation, orientation: targetPreview().orientation,

View file

@ -28,18 +28,11 @@ 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 { import {
allowChat, gameProps,
allowMarkDraw,
allowSpecials,
allowSpectators,
gameState,
menu,
mode,
reset, reset,
setGameProps,
setGameSetting, setGameSetting,
setIsReadyFor, setIsReadyFor,
setMenu,
setMode,
setTarget, setTarget,
setTargetPreview, setTargetPreview,
target, target,
@ -60,36 +53,36 @@ function EventBar(props: { clear: () => void }) {
icon: "burger-menu", icon: "burger-menu",
text: "Menu", text: "Menu",
callback: () => { callback: () => {
setMenu("menu") setGameProps("menu", "menu")
}, },
}, },
gameState() === "running" gameProps.gameState === "running"
? { ? {
icon: faSwords, icon: faSwords,
text: "Attack", text: "Attack",
callback: () => { callback: () => {
setMenu("moves") setGameProps("menu", "moves")
}, },
} }
: { : {
icon: faShip, icon: faShip,
text: "Ships", text: "Ships",
callback: () => { callback: () => {
setMenu("moves") setGameProps("menu", "moves")
}, },
}, },
{ {
icon: "pen", icon: "pen",
text: "Draw", text: "Draw",
callback: () => { callback: () => {
setMenu("draw") setGameProps("menu", "draw")
}, },
}, },
{ {
icon: "gear", icon: "gear",
text: "Settings", text: "Settings",
callback: () => { callback: () => {
setMenu("settings") setGameProps("menu", "settings")
}, },
}, },
], ],
@ -99,48 +92,46 @@ function EventBar(props: { clear: () => void }) {
text: "Surrender", text: "Surrender",
iconColor: "darkred", iconColor: "darkred",
callback: () => { callback: () => {
setMenu("surrender") setGameProps("menu", "surrender")
}, },
}, },
], ],
moves: moves:
gameState() === "running" gameProps.gameState === "running"
? [ ? [
{ {
icon: "scope", icon: "scope",
text: "Fire missile", text: "Fire missile",
enabled: mode() === 0, enabled: gameProps.mode === 0,
callback: () => { callback: () => {
setMode(0) setGameProps("mode", 0)
setTarget((t) => ({ ...t, show: false })) setTarget((t) => ({ ...t, show: false }))
}, },
}, },
{ {
icon: "torpedo", icon: "torpedo",
text: "Fire torpedo", text: "Fire torpedo",
enabled: mode() === 1 || mode() === 2, enabled: gameProps.mode === 1 || gameProps.mode === 2,
amount: amount:
2 - 2 -
(selfUser() (selfUser()?.moves.filter(
?.moves() (e) => e.type === "htorpedo" || e.type === "vtorpedo",
.filter((e) => e.type === "htorpedo" || e.type === "vtorpedo") ).length ?? 0),
.length ?? 0),
callback: () => { callback: () => {
setMode(1) setGameProps("mode", 1)
setTarget((t) => ({ ...t, show: false })) setTarget((t) => ({ ...t, show: false }))
}, },
}, },
{ {
icon: "radar", icon: "radar",
text: "Radar scan", text: "Radar scan",
enabled: mode() === 3, enabled: gameProps.mode === 3,
amount: amount:
1 - 1 -
(selfUser() (selfUser()?.moves.filter((e) => e.type === "radar").length ??
?.moves() 0),
.filter((e) => e.type === "radar").length ?? 0),
callback: () => { callback: () => {
setMode(3) setGameProps("mode", 3)
setTarget((t) => ({ ...t, show: false })) setTarget((t) => ({ ...t, show: false }))
}, },
}, },
@ -152,7 +143,7 @@ function EventBar(props: { clear: () => void }) {
amount: 1 - ships().filter((e) => e.size === 2).length, amount: 1 - ships().filter((e) => e.size === 2).length,
callback: () => { callback: () => {
if (1 - ships().filter((e) => e.size === 2).length === 0) return if (1 - ships().filter((e) => e.size === 2).length === 0) return
setMode(0) setGameProps("mode", 0)
}, },
}, },
{ {
@ -161,7 +152,7 @@ function EventBar(props: { clear: () => void }) {
amount: 3 - ships().filter((e) => e.size === 3).length, amount: 3 - ships().filter((e) => e.size === 3).length,
callback: () => { callback: () => {
if (3 - ships().filter((e) => e.size === 3).length === 0) return if (3 - ships().filter((e) => e.size === 3).length === 0) return
setMode(1) setGameProps("mode", 1)
}, },
}, },
{ {
@ -170,7 +161,7 @@ function EventBar(props: { clear: () => void }) {
amount: 2 - ships().filter((e) => e.size === 4).length, amount: 2 - ships().filter((e) => e.size === 4).length,
callback: () => { callback: () => {
if (2 - ships().filter((e) => e.size === 4).length === 0) return if (2 - ships().filter((e) => e.size === 4).length === 0) return
setMode(2) setGameProps("mode", 2)
}, },
}, },
{ {
@ -199,31 +190,31 @@ function EventBar(props: { clear: () => void }) {
{ {
icon: faGlasses, icon: faGlasses,
text: "Spectators", text: "Spectators",
disabled: !allowSpectators(), disabled: !gameProps.allowSpectators,
callback: setGameSetting({ callback: setGameSetting({
allowSpectators: !allowSpectators(), allowSpectators: !gameProps.allowSpectators,
}), }),
}, },
{ {
icon: faSparkles, icon: faSparkles,
text: "Specials", text: "Specials",
disabled: !allowSpecials(), disabled: !gameProps.allowSpecials,
callback: setGameSetting({ callback: setGameSetting({
allowSpecials: !allowSpecials(), allowSpecials: !gameProps.allowSpecials,
}), }),
}, },
{ {
icon: faComments, icon: faComments,
text: "Chat", text: "Chat",
disabled: !allowChat(), disabled: !gameProps.allowChat,
callback: setGameSetting({ allowChat: !allowChat() }), callback: setGameSetting({ allowChat: !gameProps.allowChat }),
}, },
{ {
icon: faScribble, icon: faScribble,
text: "Mark/Draw", text: "Mark/Draw",
disabled: !allowMarkDraw(), disabled: !gameProps.allowMarkDraw,
callback: setGameSetting({ callback: setGameSetting({
allowMarkDraw: !allowMarkDraw(), allowMarkDraw: !gameProps.allowMarkDraw,
}), }),
}, },
], ],
@ -243,7 +234,7 @@ function EventBar(props: { clear: () => void }) {
text: "No", text: "No",
iconColor: "red", iconColor: "red",
callback: () => { callback: () => {
setMenu("main") setGameProps("menu", "main")
}, },
}, },
], ],
@ -251,22 +242,22 @@ function EventBar(props: { clear: () => void }) {
createEffect(() => { createEffect(() => {
if ( if (
menu() !== "moves" || gameProps.menu !== "moves" ||
gameState() !== "starting" || gameProps.gameState !== "starting" ||
mode() < 0 || gameProps.mode < 0 ||
items().moves[mode()].amount items().moves[gameProps.mode].amount
) )
return return
const index = items().moves.findIndex((e) => e.amount) const index = items().moves.findIndex((e) => e.amount)
setMode(index) setGameProps("mode", index)
}) })
createEffect(() => { createEffect(() => {
setEnable(menu() === "draw") setEnable(gameProps.menu === "draw")
}) })
// createEffect(() => { // createEffect(() => {
// if (gameState() !== "running") return // if (gameProps.gameState !== "running") return
// const toastId = "otherPlayer" // const toastId = "otherPlayer"
// if (isActiveIndex) toast.dismiss(toastId) // if (isActiveIndex) toast.dismiss(toastId)
@ -293,47 +284,55 @@ function EventBar(props: { clear: () => void }) {
return ( return (
<div class="event-bar"> <div class="event-bar">
<Show when={menu() !== "main"}> <Show when={gameProps.menu !== "main"}>
<Item <Item
{...{ {...{
icon: faReply, icon: faReply,
text: "Return", text: "Return",
iconColor: "#555", iconColor: "#555",
callback: () => { callback: () => {
setMenu("main") setGameProps("menu", "main")
}, },
}} }}
/> />
</Show> </Show>
<For each={items()[menu()]}> <For each={items()[gameProps.menu]}>
{(e, i) => ( {(e, i) => (
<Show when={isActiveIndex() || menu() !== "main" || i() !== 1}> <Show
when={isActiveIndex() || gameProps.menu !== "main" || i() !== 1}
>
<Item {...e} /> <Item {...e} />
</Show> </Show>
)} )}
</For> </For>
<Show when={menu() === "moves"}> <Show when={gameProps.menu === "moves"}>
<Item <Item
{...{ {...{
icon: selfUser()?.isReady() ? faLock : faCheck, icon: selfUser()?.isReady ? faLock : faCheck,
text: selfUser()?.isReady() ? "unready" : "Done", text: selfUser()?.isReady ? "unready" : "Done",
disabled: gameState() === "starting" ? mode() >= 0 : undefined, disabled:
enabled: gameState() === "running" && mode() >= 0 && target().show, gameProps.gameState === "starting"
? gameProps.mode >= 0
: undefined,
enabled:
gameProps.gameState === "running" &&
gameProps.mode >= 0 &&
target().show,
callback: () => { callback: () => {
const i = selfIndex() const i = selfIndex()
if (i === -1) return if (i === -1) return
switch (gameState()) { switch (gameProps.gameState) {
case "starting": case "starting":
const isReady = !users[i].isReady() const isReady = !users[i]?.isReady
setIsReadyFor({ isReady, i }) setIsReadyFor({ isReady, i })
socket.emit("isReady", isReady) socket.emit("isReady", isReady)
break break
case "running": case "running":
const moves = selfUser()?.moves() const moves = selfUser()?.moves
const length = moves?.length const length = moves?.length
const props = { const props = {
type: modes[mode()].type, type: modes[gameProps.mode].type,
x: target().x, x: target().x,
y: target().y, y: target().y,
orientation: target().orientation, orientation: target().orientation,

View file

@ -11,12 +11,10 @@ import { useDraw } from "~/hooks/useDraw"
import { useDrawProps } from "~/hooks/useDrawProps" import { useDrawProps } from "~/hooks/useDrawProps"
import { import {
full, full,
gameId, gameProps,
gameState,
mode,
mouseCursor, mouseCursor,
reset, reset,
setMode, setGameProps,
setMouseCursor, setMouseCursor,
setTargetPreview, setTargetPreview,
target, target,
@ -40,27 +38,28 @@ function Gamefield() {
createEffect(() => { createEffect(() => {
if ( if (
gameState() !== "starting" || gameProps.gameState !== "starting" ||
!users[0].isReady() || !users[0]?.isReady ||
!users[1].isReady() !users[1]?.isReady
) )
return return
socket.emit("ships", ships() ?? []) socket.emit("ships", ships())
socket.emit("gameState", "running") socket.emit("gameState", "running")
}) })
createEffect(() => { createEffect(() => {
if (gameId() || !isConnected()) return if (gameProps.gameId || !isConnected) return
socket.emit("update", full) socket.emit("update", full)
}) })
createEffect(() => { createEffect(() => {
if (mode() < 0) return if (gameProps.mode < 0) return
const { x, y, show } = target() const { x, y, show } = target()
const { shouldShow, ...position } = mouseCursor() const { shouldShow, ...position } = mouseCursor()
if ( if (
!shouldShow || !shouldShow ||
(gameState() === "running" && overlapsWithAnyBorder(position, mode())) (gameProps.gameState === "running" &&
overlapsWithAnyBorder(position, gameProps.mode))
) )
setTargetPreview((t) => ({ ...t, show: false })) setTargetPreview((t) => ({ ...t, show: false }))
else { else {
@ -71,14 +70,17 @@ function Gamefield() {
})) }))
const handleKeyPress = (event: KeyboardEvent) => { const handleKeyPress = (event: KeyboardEvent) => {
if (event.key !== "r") return if (event.key !== "r") return
if (gameState() === "starting") { if (gameProps.gameState === "starting") {
setTargetPreview((t) => ({ setTargetPreview((t) => ({
...t, ...t,
orientation: t.orientation === "h" ? "v" : "h", orientation: t.orientation === "h" ? "v" : "h",
})) }))
} }
if (gameState() === "running" && (mode() === 1 || mode() === 2)) if (
setMode(mode() === 1 ? 2 : 1) gameProps.gameState === "running" &&
(gameProps.mode === 1 || gameProps.mode === 2)
)
setGameProps("mode", gameProps.mode === 1 ? 2 : 1)
} }
document.addEventListener("keydown", handleKeyPress) document.addEventListener("keydown", handleKeyPress)
onCleanup(() => { onCleanup(() => {
@ -88,7 +90,7 @@ function Gamefield() {
}) })
createEffect(() => { createEffect(() => {
if (gameState() !== "aborted") return if (gameProps.gameState !== "aborted") return
// toast.info("Enemy gave up!") // toast.info("Enemy gave up!")
navigator("/") navigator("/")
reset() reset()

View file

@ -1,5 +1,5 @@
import { For, Show } from "solid-js" import { For, Show } from "solid-js"
import { gameState } from "~/hooks/useGameProps" import { gameProps } from "~/hooks/useGameProps"
import { useSession } from "~/hooks/useSession" import { useSession } from "~/hooks/useSession"
import Ship from "./Ship" import Ship from "./Ship"
@ -7,8 +7,8 @@ function Ships() {
const { isActiveIndex, selfUser } = useSession() const { isActiveIndex, selfUser } = useSession()
return ( return (
<Show when={gameState() !== "running" || !isActiveIndex()}> <Show when={gameProps.gameState !== "running" || !isActiveIndex()}>
<For each={selfUser()?.ships()}>{(props) => <Ship {...props} />}</For> <For each={selfUser()?.ships}>{(props) => <Ship {...props} />}</For>
</Show> </Show>
) )
} }

View file

@ -1,7 +1,8 @@
import { For, Match, Switch } from "solid-js" import { For, Match, Switch } from "solid-js"
import { gameState, mode, target, targetPreview } from "~/hooks/useGameProps" import { gameProps, target, targetPreview } from "~/hooks/useGameProps"
import { useSession } from "~/hooks/useSession" import { useSession } from "~/hooks/useSession"
import { import {
compiledHits,
composeTargetTiles, composeTargetTiles,
intersectingShip, intersectingShip,
shipProps, shipProps,
@ -11,29 +12,39 @@ import HitElems from "./HitElems"
import Ship from "./Ship" import Ship from "./Ship"
function Targets() { function Targets() {
const { activeUser, ships } = useSession() const { activeIndex, ships } = useSession()
const ship = () => shipProps(ships(), mode(), targetPreview()) const ship = () => shipProps(ships(), gameProps.mode, targetPreview())
const intersectionProps = () => intersectingShip(ships(), ship()) const intersectionProps = () => intersectingShip(ships(), ship())
return ( return (
<Switch> <Switch>
<Match when={gameState() === "running"}> <Match when={gameProps.gameState === "running"}>
<For each={composeTargetTiles(target(), mode(), activeUser().hits())}> <For
each={composeTargetTiles(
target(),
gameProps.mode,
compiledHits(activeIndex() === 0 ? 1 : 0),
)}
>
{(props) => <GamefieldPointer {...props} />} {(props) => <GamefieldPointer {...props} />}
</For> </For>
<For <For
each={composeTargetTiles( each={composeTargetTiles(
targetPreview(), targetPreview(),
mode(), gameProps.mode,
activeUser().hits(), compiledHits(activeIndex() === 0 ? 1 : 0),
)} )}
> >
{(props) => <GamefieldPointer {...props} preview />} {(props) => <GamefieldPointer {...props} preview />}
</For> </For>
</Match> </Match>
<Match <Match
when={gameState() === "starting" && mode() >= 0 && targetPreview().show} when={
gameProps.gameState === "starting" &&
gameProps.mode >= 0 &&
targetPreview().show
}
> >
<Ship <Ship
{...ship()} {...ship()}

View file

@ -5,15 +5,7 @@ import {
import { JSX, Show, createEffect, createSignal, onCleanup } from "solid-js" import { JSX, Show, createEffect, createSignal, onCleanup } from "solid-js"
import { useNavigate } from "solid-start" import { useNavigate } from "solid-start"
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon" import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
import { import { full, gameProps, leave, reset, users } from "~/hooks/useGameProps"
full,
gameId,
gamePin,
gameState,
leave,
reset,
users,
} from "~/hooks/useGameProps"
import { useSession } from "~/hooks/useSession" import { useSession } from "~/hooks/useSession"
import useSocket from "~/hooks/useSocket" import useSocket from "~/hooks/useSocket"
import { socket } from "~/lib/socket" import { socket } from "~/lib/socket"
@ -45,7 +37,7 @@ function LobbyFrame(props: { openSettings: () => void }) {
const navigator = useNavigate() const navigator = useNavigate()
const { session } = useSession() const { session } = useSession()
const [launchTime, setLaunchTime] = createSignal(3) const [launchTime, setLaunchTime] = createSignal(3)
const launching = () => users[0].isReady() && users[1].isReady() const launching = () => users[0]?.isReady && users[1]?.isReady
createEffect(() => { createEffect(() => {
if (!launching() || launchTime() > 0) return if (!launching() || launchTime() > 0) return
@ -64,12 +56,13 @@ function LobbyFrame(props: { openSettings: () => void }) {
}) })
createEffect(() => { createEffect(() => {
if (gameId() || !isConnected()) return if (gameProps.gameId || !isConnected) return
socket.emit("update", full) socket.emit("update", full)
}) })
createEffect(() => { createEffect(() => {
if (gameState() === "unknown" || gameState() === "lobby") return if (gameProps.gameState === "unknown" || gameProps.gameState === "lobby")
return
navigator("/gamefield") navigator("/gamefield")
}) })
@ -90,10 +83,10 @@ function LobbyFrame(props: { openSettings: () => void }) {
> >
{"Game-PIN: "} {"Game-PIN: "}
<Show <Show
when={isConnected()} when={isConnected}
fallback={<FontAwesomeIcon icon={faSpinnerThird} spin />} fallback={<FontAwesomeIcon icon={faSpinnerThird} spin />}
> >
<span class="underline">{gamePin() ?? "----"}</span> <span class="underline">{gameProps.gamePin ?? "----"}</span>
</Show> </Show>
</Show> </Show>
</h1> </h1>
@ -103,7 +96,7 @@ function LobbyFrame(props: { openSettings: () => void }) {
</div> </div>
<div class="flex items-center justify-around"> <div class="flex items-center justify-around">
<Show <Show
when={isConnected()} when={isConnected}
fallback={ fallback={
<p class="font-farro m-48 text-center text-6xl font-medium"> <p class="font-farro m-48 text-center text-6xl font-medium">
Warte auf Verbindung Warte auf Verbindung
@ -113,7 +106,7 @@ function LobbyFrame(props: { openSettings: () => void }) {
<> <>
<Player src="player_blue.png" i={0} userId={session()?.user?.id} /> <Player src="player_blue.png" i={0} userId={session()?.user?.id} />
<p class="font-farro m-4 text-6xl font-semibold">VS</p> <p class="font-farro m-4 text-6xl font-semibold">VS</p>
{users[1].id() ? ( {users[1] ? (
<Player src="player_red.png" i={1} userId={session()?.user?.id} /> <Player src="player_red.png" i={1} userId={session()?.user?.id} />
) : ( ) : (
<p class="font-farro w-96 text-center text-4xl font-medium"> <p class="font-farro w-96 text-center text-4xl font-medium">

View file

@ -44,9 +44,7 @@ function HourGlass() {
function Player(props: { src: string; i: 0 | 1; userId?: string }) { function Player(props: { src: string; i: 0 | 1; userId?: string }) {
const player = () => users[props.i] const player = () => users[props.i]
const isReady = () => users[props.i].isReady() const primary = () => props.userId && props.userId === player()?.id
const isConnected = () => users[props.i].isConnected()
const primary = () => props.userId && props.userId === 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">
@ -56,7 +54,7 @@ function Player(props: { src: string; i: 0 | 1; userId?: string }) {
primary() ? "font-semibold" : "font-normal", primary() ? "font-semibold" : "font-normal",
)} )}
> >
{player().name() ?? "Spieler " + (props.i === 1 ? "2" : "1")} {player()?.name ?? "Spieler " + (props.i === 1 ? "2" : "1")}
</p> </p>
<div class="relative"> <div class="relative">
<img <img
@ -74,21 +72,27 @@ function Player(props: { src: string; i: 0 | 1; userId?: string }) {
</Show> </Show>
</div> </div>
<Button <Button
type={isConnected() ? (isReady() ? "green" : "orange") : "gray"} type={
player()?.isConnected
? player()?.isReady
? "green"
: "orange"
: "gray"
}
latching latching
isLatched={!!isReady()} isLatched={player()?.isReady}
onClick={() => { onClick={() => {
if (!player()) return if (!player()) return
socket.emit("isReady", !isReady()) socket.emit("isReady", !player()?.isReady)
setIsReadyFor({ setIsReadyFor({
i: props.i, i: props.i,
isReady: !isReady(), isReady: !player()?.isReady,
}) })
}} }}
disabled={!primary()} disabled={!primary()}
> >
Ready Ready
{isReady() && isConnected() ? ( {player()?.isReady && player()?.isConnected ? (
<FontAwesomeIcon icon={faCheck} class="ml-4 w-12" /> <FontAwesomeIcon icon={faCheck} class="ml-4 w-12" />
) : primary() ? ( ) : primary() ? (
<FontAwesomeIcon <FontAwesomeIcon

View file

@ -3,11 +3,10 @@ import { socket } from "~/lib/socket"
import { GamePropsSchema, GameState } from "~/lib/zodSchemas" import { GamePropsSchema, GameState } from "~/lib/zodSchemas"
// import { toast } from "react-toastify" // import { toast } from "react-toastify"
import { createSignal } from "solid-js" import { createSignal } from "solid-js"
import { createStore } from "solid-js/store"
import { getPayloadFromProps } from "~/lib/getPayloadFromProps" import { getPayloadFromProps } from "~/lib/getPayloadFromProps"
import { getPayloadwithChecksum } from "~/lib/getPayloadwithChecksum" import { getPayloadwithChecksum } from "~/lib/getPayloadwithChecksum"
import { import {
compileHits,
initialUser,
initlialMouseCursor, initlialMouseCursor,
initlialTarget, initlialTarget,
initlialTargetPreview, initlialTargetPreview,
@ -17,81 +16,75 @@ import {
GameSettings, GameSettings,
MouseCursor, MouseCursor,
MoveDispatchProps, MoveDispatchProps,
NewUsers,
ShipProps, ShipProps,
Target, Target,
TargetPreview, TargetPreview,
User,
Users,
} from "../interfaces/frontend" } from "../interfaces/frontend"
export const [hash, setHash] = createSignal<string | null>(null) export interface GameProps {
export const [gamePin, setGamePin] = createSignal<string | null>(null) hash: string | null
export const [gameId, setGameId] = createSignal<string>("") gamePin: string | null
export const [gameState, setGameState] = createSignal<GameState>("unknown") gameId: string
export const [allowChat, setAllowChat] = createSignal(false) gameState: GameState
export const [allowMarkDraw, setAllowMarkDraw] = createSignal(false) allowChat: boolean
export const [allowSpecials, setAllowSpecials] = createSignal(false) allowMarkDraw: boolean
export const [allowSpectators, setallowSpectators] = createSignal(false) allowSpecials: boolean
export const [menu, setMenu] = createSignal<keyof EventBarModes>("moves") allowSpectators: boolean
export const [mode, setMode] = createSignal(0) menu: keyof EventBarModes
mode: number
}
const initialGameProps = {
hash: null,
gamePin: null,
gameId: "",
gameState: "unknown",
allowChat: false,
allowMarkDraw: false,
allowSpecials: false,
allowSpectators: false,
menu: "moves",
mode: 0,
} satisfies GameProps
export const [gameProps, setGameProps] =
createStore<GameProps>(initialGameProps)
const initialUsers = { 0: null, 1: null }
export const [users, setUsers] = createStore<Users>(initialUsers)
export const [target, setTarget] = createSignal<Target>(initlialTarget) export const [target, setTarget] = createSignal<Target>(initlialTarget)
export const [targetPreview, setTargetPreview] = createSignal<TargetPreview>( export const [targetPreview, setTargetPreview] = createSignal<TargetPreview>(
initlialTargetPreview, initlialTargetPreview,
) )
export const [mouseCursor, setMouseCursor] = export const [mouseCursor, setMouseCursor] =
createSignal<MouseCursor>(initlialMouseCursor) createSignal<MouseCursor>(initlialMouseCursor)
export const users = {
0: {
...initialUser(),
hits: () => compileHits(users, 0),
},
1: { ...initialUser(), hits: () => compileHits(users, 1) },
forEach(cb: (user: ReturnType<typeof initialUser>, i: 0 | 1) => void) {
cb(this[0], 0)
cb(this[1], 1)
},
map<T>(cb: (user: ReturnType<typeof initialUser>, i: 0 | 1) => T) {
return { 0: cb(this[0], 0), 1: cb(this[1], 1) }
},
}
export type Users = typeof users
export function DispatchMove(move: MoveDispatchProps, index: 0 | 1) { export function DispatchMove(move: MoveDispatchProps, index: 0 | 1) {
users[index].setMoves((e) => [...e, move]) setUsers(index, "moves", (e) => [...e, move])
} }
export function setShips(ships: ShipProps[], index: number) { export function setShips(ships: ShipProps[], index: 0 | 1) {
users.forEach(({ setShips }, i) => { setUsers(index, "ships", ships)
if (index !== i) return
setShips(ships)
})
} }
export function removeShip({ size, variant, x, y }: ShipProps, index: number) { export function removeShip({ size, variant, x, y }: ShipProps, index: 0 | 1) {
users.forEach((user, i) => { setUsers(index, "ships", (ships) => {
if (index !== i) return const indexToRemove = ships.findIndex(
const indexToRemove = user (ship) =>
.ships() ship.size === size &&
.findIndex( ship.variant === variant &&
(ship) => ship.x === x &&
ship.size === size && ship.y === y,
ship.variant === variant && )
ship.x === x &&
ship.y === y, return ships.filter((_, i) => i !== indexToRemove)
)
user.setShips((ships) => ships.filter((_, i) => i !== indexToRemove))
}) })
} }
export function setPlayer(newUsers: NewUsers): string | null { export function setPlayer(newUsers: Users): string | null {
let hash: string | null = null let hash: string | null = null
users.forEach((user, i) => { setUsers(newUsers)
const newUser = newUsers[i]
if (!newUser) return defaultUser(user)
user.setId(newUser.id)
user.setName(newUser.name)
user.setChats(newUser.chats)
user.setMoves(newUser.moves)
user.setShips(newUser.ships)
})
const body = getPayloadwithChecksum(getPayloadFromProps()) const body = getPayloadwithChecksum(getPayloadFromProps())
if (!body.hash) { if (!body.hash) {
console.log("Something is wrong... ") console.log("Something is wrong... ")
@ -102,15 +95,15 @@ export function setPlayer(newUsers: NewUsers): string | null {
return null return null
} }
hash = body.hash hash = body.hash
setHash(hash) setGameProps("hash", hash)
return hash return hash
} }
export function setSetting(newSettings: GameSettings): string | null { export function setSetting(newSettings: GameSettings): string | null {
let hash: string | null = null let hash: string | null = null
setAllowChat((e) => newSettings.allowChat ?? e) setGameProps("allowChat", (e) => newSettings.allowChat ?? e)
setAllowMarkDraw((e) => newSettings.allowMarkDraw ?? e) setGameProps("allowMarkDraw", (e) => newSettings.allowMarkDraw ?? e)
setAllowSpecials((e) => newSettings.allowSpecials ?? e) setGameProps("allowSpecials", (e) => newSettings.allowSpecials ?? e)
setallowSpectators((e) => newSettings.allowSpectators ?? e) setGameProps("allowSpectators", (e) => newSettings.allowSpectators ?? e)
const body = getPayloadwithChecksum(getPayloadFromProps()) const body = getPayloadwithChecksum(getPayloadFromProps())
if (!body.hash) { if (!body.hash) {
console.log("Something is wrong... ") console.log("Something is wrong... ")
@ -121,7 +114,7 @@ export function setSetting(newSettings: GameSettings): string | null {
return null return null
} }
hash = body.hash hash = body.hash
setHash(hash) setGameProps("hash", hash)
return hash return hash
} }
@ -137,36 +130,41 @@ export function setGameSetting(newSettings: GameSettings) {
} }
export function full(newProps: GamePropsSchema) { export function full(newProps: GamePropsSchema) {
if (hash() === newProps.hash) { if (gameProps.hash === newProps.hash) {
console.log("Everything up to date.") console.log("Everything up to date.")
} else { } else {
console.log("Update was needed.", hash(), newProps.hash) console.log("Update was needed.", gameProps.hash, newProps.hash)
if (gameId() !== newProps.payload?.game?.id) if (gameProps.gameId !== newProps.payload?.game?.id)
console.warn( console.warn(
"Different gameId detected on update: ", "Different gameId detected on update: ",
gameId(), gameProps.gameId,
newProps.payload?.game?.id, newProps.payload?.game?.id,
) )
setHash(newProps.hash) setGameProps({
setGamePin(newProps.payload.gamePin) hash: newProps.hash,
setGameId(newProps.payload.game?.id ?? "") gamePin: newProps.payload.gamePin,
setGameState(newProps.payload.game?.state ?? "unknown") gameId: newProps.payload.game?.id ?? "",
setAllowChat(newProps.payload.game?.allowChat ?? false) gameState: newProps.payload.game?.state ?? "unknown",
setAllowMarkDraw(newProps.payload.game?.allowMarkDraw ?? false) allowChat: newProps.payload.game?.allowChat ?? false,
setAllowSpecials(newProps.payload.game?.allowSpecials ?? false) allowMarkDraw: newProps.payload.game?.allowMarkDraw ?? false,
setallowSpectators(newProps.payload.game?.allowSpectators ?? false) allowSpecials: newProps.payload.game?.allowSpecials ?? false,
users.forEach((user, i) => { allowSpectators: newProps.payload.game?.allowSpectators ?? false,
const newUser = newProps.payload.users[i]
if (!newUser) return
user.setId(newUser.id)
user.setName(newUser.name)
user.setChats(newUser.chats)
user.setMoves(newUser.moves)
user.setShips(newUser.ships)
}) })
const compiledUsers = [
newProps.payload.users[0],
newProps.payload.users[1],
].map((user) =>
user
? {
...user,
isReady: false,
isConnected: false,
}
: null,
) as [User, User]
setUsers({ 0: compiledUsers[0], 1: compiledUsers[1] })
} }
} }
export function leave(cb: () => void) { export function leave(cb: () => void) {
@ -179,12 +177,12 @@ export function leave(cb: () => void) {
}) })
} }
export function setIsReadyFor({ i, isReady }: { i: 0 | 1; isReady: boolean }) { export function setIsReadyFor({ i, isReady }: { i: 0 | 1; isReady: boolean }) {
users[i].setIsReady(isReady) setUsers(i, (e) => ({ ...e, isReady, isConnected: true }))
users[i].setIsConnected(true)
} }
export function newGameState(newState: GameState) { export function newGameState(newState: GameState) {
setGameState(newState) setGameProps("gameState", newState)
users.forEach((e) => e.setIsReady(false)) setUsers(0, (e) => (e && e.isReady ? { isReady: false } : e))
setUsers(1, (e) => (e && e.isReady ? { isReady: false } : e))
} }
export function setIsConnectedFor({ export function setIsConnectedFor({
i, i,
@ -193,34 +191,15 @@ export function setIsConnectedFor({
i: 0 | 1 i: 0 | 1
isConnected: boolean isConnected: boolean
}) { }) {
users[i].setIsConnected(isConnected) setUsers(i, "isConnected", isConnected)
if (isConnected) return if (isConnected) return
users[i].setIsReady(false) setUsers(i, "isReady", false)
} }
export function reset() { export function reset() {
setHash(null) setGameProps(initialGameProps)
setGamePin(null)
setGameId("")
setGameState("unknown")
setallowSpectators(false)
setAllowSpecials(false)
setAllowChat(false)
setAllowMarkDraw(false)
setMenu("moves")
setMode(0)
setTarget(initlialTarget) setTarget(initlialTarget)
setTargetPreview(initlialTargetPreview) setTargetPreview(initlialTargetPreview)
setMouseCursor(initlialMouseCursor) setMouseCursor(initlialMouseCursor)
users.forEach(defaultUser) setUsers(initialUsers)
}
function defaultUser(user: ReturnType<typeof initialUser>) {
user.setIsReady(false)
user.setIsConnected(false)
user.setId("")
user.setName("")
user.setChats([])
user.setMoves([])
user.setShips([])
} }

View file

@ -9,14 +9,15 @@ import {
useContext, useContext,
} from "solid-js" } from "solid-js"
import { useIsRouting } from "solid-start" import { useIsRouting } from "solid-start"
import { gameState, setMenu, setMode, users } from "./useGameProps" import { gameProps, setGameProps, users } from "./useGameProps"
const [state, setState] = createSignal<Session | null | undefined>(undefined) const [state, setState] = createSignal<Session | null | undefined>(undefined)
const selfIndex = () => { const selfIndex = () => {
switch (state()?.user?.id) { switch (state()?.user?.id) {
case users[0].id(): case users[0]?.id:
return 0 return 0
case users[1].id(): case users[1]?.id:
return 1 return 1
default: default:
return -1 return -1
@ -24,9 +25,9 @@ const selfIndex = () => {
} }
const activeIndex = () => { const activeIndex = () => {
if (gameState() !== "running") return 0 if (gameProps.gameState !== "running") return 0
const l1 = users[0].moves().length const l1 = users[0]?.moves.length ?? 0
const l2 = users[1].moves().length const l2 = users[1]?.moves.length ?? 0
return l1 > l2 ? 1 : 0 return l1 > l2 ? 1 : 0
} }
@ -48,7 +49,8 @@ const selfUser = () => {
*/ */
const activeUser = () => users[activeIndex() === 0 ? 1 : 0] const activeUser = () => users[activeIndex() === 0 ? 1 : 0]
const ships = () => selfUser()?.ships() ?? [] const ships = () => selfUser()?.ships ?? []
const contextValue = { const contextValue = {
session: state, session: state,
selfIndex, selfIndex,
@ -82,13 +84,13 @@ export function SessionProvider(props: { children: JSX.Element }) {
}) })
createEffect(() => { createEffect(() => {
if (gameState() !== "running") return if (gameProps.gameState !== "running") return
if (activeIndex() === selfIndex()) { if (activeIndex() === selfIndex()) {
setMenu("moves") setGameProps("menu", "moves")
setMode(0) setGameProps("mode", 0)
} else { } else {
setMenu("main") setGameProps("menu", "main")
setMode(-1) setGameProps("mode", -1)
} }
}) })

View file

@ -3,14 +3,15 @@ import status from "http-status"
import { createEffect, createSignal, onCleanup } from "solid-js" import { createEffect, createSignal, onCleanup } 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 { frontendUsers } from "~/lib/utils/helpers"
import { GamePropsSchema, GameState } from "~/lib/zodSchemas"
import { isAuthenticated } from "~/routes/start" import { isAuthenticated } from "~/routes/start"
import { GameSettings, PlayerEvent } from "../interfaces/frontend" import { GameSettings, PlayerEvent } from "../interfaces/frontend"
import { import {
DispatchMove, DispatchMove,
full, full,
gameId, gameProps,
setGameState, setGameProps,
setIsConnectedFor, setIsConnectedFor,
setIsReadyFor, setIsReadyFor,
setPlayer, setPlayer,
@ -29,7 +30,7 @@ function useSocket() {
const isConnected = () => { const isConnected = () => {
const i = selfIndex() const i = selfIndex()
return i !== -1 return i !== -1
? users[i].isConnected() && isConnectedState() ? users[i]?.isConnected && isConnectedState()
: isConnectedState() : isConnectedState()
} }
@ -87,7 +88,8 @@ function useSocket() {
isConnected: true, isConnected: true,
}) })
const index = selfIndex() const index = selfIndex()
if (index !== -1) socket.emit("isReady", users[index].isReady()) if (index !== -1)
socket.emit("isReady", users[index]?.isReady ?? false)
message = "Player has joined the lobby." message = "Player has joined the lobby."
break break
@ -100,7 +102,7 @@ function useSocket() {
if (type === "disconnect") return if (type === "disconnect") return
const { hash } = event const { hash } = event
const newHash = setPlayer(event.users) const newHash = setPlayer(frontendUsers(event.users))
if (!newHash || newHash === hash) return if (!newHash || newHash === hash) return
console.log("hash", hash, newHash) console.log("hash", hash, newHash)
socket.emit("update", (body) => { socket.emit("update", (body) => {
@ -109,6 +111,8 @@ function useSocket() {
}) })
} }
const setGameState = (state: GameState) => setGameProps("gameState", state)
const gameSetting = (newSettings: GameSettings, hash: string) => { const gameSetting = (newSettings: GameSettings, hash: string) => {
const newHash = setSetting(newSettings) const newHash = setSetting(newSettings)
if (!newHash || newHash === hash) return if (!newHash || newHash === hash) return
@ -148,7 +152,7 @@ function useSocket() {
}) })
createEffect(() => { createEffect(() => {
if (!gameId()) { if (!gameProps.gameId) {
socket.disconnect() socket.disconnect()
fetch("/api/game/running", { fetch("/api/game/running", {
method: "GET", method: "GET",

View file

@ -26,11 +26,11 @@ 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: number) => void "canvas-state-from-server": (state: string, userIndex: 0 | 1) => void
"draw-line": (props: DrawLineProps, userIndex: number) => void "draw-line": (props: DrawLineProps, userIndex: 0 | 1) => void
"canvas-clear": () => void "canvas-clear": () => void
gameState: (newState: GameState) => void gameState: (newState: GameState) => void
ships: (ships: ShipProps[], index: number) => void ships: (ships: ShipProps[], index: 0 | 1) => void
dispatchMove: (props: MoveDispatchProps, i: 0 | 1) => void dispatchMove: (props: MoveDispatchProps, i: 0 | 1) => void
} }
@ -58,7 +58,7 @@ interface SocketData {
props: { props: {
userId: string userId: string
gameId: string gameId: string
index: number index: 0 | 1
} }
user: Session["user"] user: Session["user"]
gameId: string gameId: string

View file

@ -81,11 +81,15 @@ export type PlayerEvent =
| { | {
type: "connect" | "leave" type: "connect" | "leave"
i: 0 | 1 i: 0 | 1
users: NewUsers users: Players
hash: string hash: string
} }
| { | {
type: "disconnect" type: "disconnect"
i: 0 | 1 i: 0 | 1
} }
export type NewUsers = { 0: PlayerSchema | null; 1: PlayerSchema | null } export type Players = { 0: PlayerSchema; 1: PlayerSchema }
export type User =
| (PlayerSchema & { isReady: boolean; isConnected: boolean })
| null
export type Users = { 0: User; 1: User }

View file

@ -1,32 +1,28 @@
import { import { gameProps, users } from "~/hooks/useGameProps"
allowChat,
allowMarkDraw,
allowSpecials,
allowSpectators,
gameId,
gamePin,
gameState,
users,
} from "~/hooks/useGameProps"
export function getPayloadFromProps() { export function getPayloadFromProps() {
const reducedUsers = [users[0], users[1]].map((user, i) =>
user
? {
index: i,
id: user.id,
name: user.name,
chats: user.chats,
moves: user.moves,
ships: user.ships,
}
: null,
)
return { return {
game: { game: {
id: gameId(), id: gameProps.gameId,
state: gameState(), state: gameProps.gameState,
allowChat: allowChat(), allowChat: gameProps.allowChat,
allowMarkDraw: allowMarkDraw(), allowMarkDraw: gameProps.allowMarkDraw,
allowSpecials: allowSpecials(), allowSpecials: gameProps.allowSpecials,
allowSpectators: allowSpectators(), allowSpectators: gameProps.allowSpectators,
}, },
gamePin: gamePin(), gamePin: gameProps.gamePin,
users: users.map((user, i) => ({ users: { 0: reducedUsers[0], 1: reducedUsers[1] },
index: i,
id: user.id(),
name: user.name(),
chats: user.chats(),
moves: user.moves(),
ships: user.ships(),
})),
} }
} }

View file

@ -1,18 +1,19 @@
import { createSignal } from "solid-js"
import { count } from "~/components/Gamefield/Gamefield" import { count } from "~/components/Gamefield/Gamefield"
import { Users } from "~/hooks/useGameProps" import { users } from "~/hooks/useGameProps"
import type { import type {
Hit, Hit,
IndexedPosition, IndexedPosition,
Mode, Mode,
Players,
PointerProps, PointerProps,
Position, Position,
ShipProps, ShipProps,
Target, Target,
TargetList, TargetList,
TargetPreview, TargetPreview,
User,
} from "../../interfaces/frontend" } from "../../interfaces/frontend"
import { ChatSchema, MoveSchema, MoveType, Orientation } from "../zodSchemas" import { MoveType, Orientation } from "../zodSchemas"
export function borderCN(count: number, x: number, y: number) { export function borderCN(count: number, x: number, y: number) {
if (x === 0) return "left" if (x === 0) return "left"
@ -131,31 +132,6 @@ export const initlialMouseCursor = {
x: 0, x: 0,
y: 0, y: 0,
} }
export function initialUser() {
const [isReady, setIsReady] = createSignal(false)
const [isConnected, setIsConnected] = createSignal(false)
const [id, setId] = createSignal<string>("")
const [name, setName] = createSignal<string>("")
const [chats, setChats] = createSignal<ChatSchema[]>([])
const [moves, setMoves] = createSignal<MoveSchema[]>([])
const [ships, setShips] = createSignal<ShipProps[]>([])
return {
isReady,
setIsReady,
isConnected,
setIsConnected,
id,
setId,
name,
setName,
chats,
setChats,
moves,
setMoves,
ships,
setShips,
}
}
export const shipProps = ( export const shipProps = (
ships: ShipProps[], ships: ShipProps[],
@ -249,21 +225,37 @@ export function intersectingShip(
} }
} }
export function compileHits(users: Users, i: 0 | 1) { export function compiledHits(i: 0 | 1) {
return users[i === 0 ? 1 : 0].moves().reduce((hits, move) => { return (
const list = targetList(move, move.type) users[i === 0 ? 1 : 0]?.moves.reduce((hits, move) => {
if (move.type === MoveType.Enum.radar) return hits const list = targetList(move, move.type)
return [ return move.type === MoveType.Enum.radar
...hits, ? hits
...list.map(({ x, y }) => ({ : [
hit: !!intersectingShip(users[i].ships(), { ...hits,
...move, ...list.map(({ x, y }) => ({
size: 1, hit: !!intersectingShip(users[i]?.ships ?? [], {
variant: 0, ...move,
}).fields.length, size: 1,
x, variant: 0,
y, }).fields.length,
})), x,
] y,
}, [] as Hit[]) })),
]
}, [] as Hit[]) ?? []
)
}
export const frontendUsers = (users: Players) => {
const compiledUsers = [users[0], users[1]].map((user) =>
user
? {
...user,
isReady: false,
isConnected: false,
}
: null,
) as [User, User]
return { 0: compiledUsers[0], 1: compiledUsers[1] }
} }