user states improvements and fixes
This commit is contained in:
parent
252f6f6028
commit
efcb61b1ed
19 changed files with 309 additions and 273 deletions
|
@ -30,6 +30,7 @@
|
|||
"drizzle-orm": "^0.28.6",
|
||||
"drizzle-zod": "^0.5.1",
|
||||
"http-status": "^1.7.0",
|
||||
"json-stable-stringify": "^1.0.2",
|
||||
"nodemailer": "^6.9.5",
|
||||
"object-hash": "^3.0.0",
|
||||
"postgres": "^3.3.5",
|
||||
|
@ -44,6 +45,7 @@
|
|||
"devDependencies": {
|
||||
"@playwright/test": "^1.37.1",
|
||||
"@total-typescript/ts-reset": "^0.4.2",
|
||||
"@types/json-stable-stringify": "^1.0.34",
|
||||
"@types/node": "^20.5.9",
|
||||
"@types/nodemailer": "^6.4.9",
|
||||
"@types/object-hash": "^3.0.4",
|
||||
|
|
20
leaky-ships/pnpm-lock.yaml
generated
20
leaky-ships/pnpm-lock.yaml
generated
|
@ -66,6 +66,9 @@ dependencies:
|
|||
http-status:
|
||||
specifier: ^1.7.0
|
||||
version: 1.7.0
|
||||
json-stable-stringify:
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.2
|
||||
nodemailer:
|
||||
specifier: ^6.9.5
|
||||
version: 6.9.5
|
||||
|
@ -101,6 +104,9 @@ devDependencies:
|
|||
'@total-typescript/ts-reset':
|
||||
specifier: ^0.4.2
|
||||
version: 0.4.2
|
||||
'@types/json-stable-stringify':
|
||||
specifier: ^1.0.34
|
||||
version: 1.0.34
|
||||
'@types/node':
|
||||
specifier: ^20.5.9
|
||||
version: 20.5.9
|
||||
|
@ -2050,6 +2056,10 @@ packages:
|
|||
resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==}
|
||||
dev: true
|
||||
|
||||
/@types/json-stable-stringify@1.0.34:
|
||||
resolution: {integrity: sha512-s2cfwagOQAS8o06TcwKfr9Wx11dNGbH2E9vJz1cqV+a/LOyhWNLUNd6JSRYNzvB4d29UuJX2M0Dj9vE1T8fRXw==}
|
||||
dev: true
|
||||
|
||||
/@types/ms@0.7.31:
|
||||
resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
|
||||
|
||||
|
@ -4008,6 +4018,12 @@ packages:
|
|||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||
dev: true
|
||||
|
||||
/json-stable-stringify@1.0.2:
|
||||
resolution: {integrity: sha512-eunSSaEnxV12z+Z73y/j5N37/In40GK4GmsSy+tEHJMxknvqnA7/djeYtAgW0GsWHUfg+847WJjKaEylk2y09g==}
|
||||
dependencies:
|
||||
jsonify: 0.0.1
|
||||
dev: false
|
||||
|
||||
/json5@2.2.3:
|
||||
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
|
||||
engines: {node: '>=6'}
|
||||
|
@ -4020,6 +4036,10 @@ packages:
|
|||
optionalDependencies:
|
||||
graceful-fs: 4.2.11
|
||||
|
||||
/jsonify@0.0.1:
|
||||
resolution: {integrity: sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==}
|
||||
dev: false
|
||||
|
||||
/jsx-ast-utils@3.3.5:
|
||||
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
|
||||
engines: {node: '>=4.0'}
|
||||
|
|
|
@ -31,18 +31,22 @@ type TilesType = {
|
|||
y: number
|
||||
}
|
||||
|
||||
function BorderTiles() {
|
||||
const { selfIndex, ships } = useSession()
|
||||
|
||||
const settingTarget = (isGameTile: boolean, x: number, y: number) => {
|
||||
const sIndex = selfIndex()
|
||||
if (sIndex === -1) return
|
||||
function settingTarget(
|
||||
isGameTile: boolean,
|
||||
x: number,
|
||||
y: number,
|
||||
index: 0 | 1,
|
||||
{
|
||||
activeIndex,
|
||||
ships,
|
||||
}: Pick<ReturnType<typeof useSession>, "activeIndex" | "ships">,
|
||||
) {
|
||||
if (gameProps.gameState === "running") {
|
||||
const list = targetList(targetPreview(), gameProps.mode)
|
||||
if (
|
||||
!isGameTile ||
|
||||
!list.filter(
|
||||
({ x, y }) => !isAlreadyHit(x, y, compiledHits(sIndex === 0 ? 1 : 0)),
|
||||
({ x, y }) => !isAlreadyHit(x, y, compiledHits(activeIndex())),
|
||||
).length
|
||||
)
|
||||
return
|
||||
|
@ -64,10 +68,67 @@ function BorderTiles() {
|
|||
setMouseCursor((e) => ({ ...e, shouldShow: false }))
|
||||
setShips(
|
||||
[...ships(), shipProps(ships(), gameProps.mode, targetPreview())],
|
||||
sIndex,
|
||||
index,
|
||||
)
|
||||
}
|
||||
}
|
||||
function onClick(
|
||||
props: TilesType,
|
||||
{ selfIndex, activeIndex, ships }: ReturnType<typeof useSession>,
|
||||
) {
|
||||
const sIndex = selfIndex()
|
||||
if (!sIndex) return
|
||||
if (gameProps.gameState === "running") {
|
||||
settingTarget(props.isGameTile, props.x, props.y, sIndex.i, {
|
||||
activeIndex,
|
||||
ships,
|
||||
})
|
||||
} else if (gameProps.gameState === "starting") {
|
||||
const { index } = intersectingShip(ships(), {
|
||||
...mouseCursor(),
|
||||
size: 1,
|
||||
variant: 0,
|
||||
orientation: "h",
|
||||
})
|
||||
if (typeof index === "undefined")
|
||||
settingTarget(props.isGameTile, props.x, props.y, sIndex.i, {
|
||||
activeIndex,
|
||||
ships,
|
||||
})
|
||||
else {
|
||||
const ship = ships()[index]
|
||||
setGameProps("mode", ship.size - 2)
|
||||
removeShip(ship, sIndex.i)
|
||||
setMouseCursor((e) => ({ ...e, shouldShow: true }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseEnter(
|
||||
props: TilesType,
|
||||
{ ships }: ReturnType<typeof useSession>,
|
||||
) {
|
||||
setMouseCursor({
|
||||
x: props.x,
|
||||
y: props.y,
|
||||
shouldShow:
|
||||
props.isGameTile &&
|
||||
(gameProps.gameState === "starting"
|
||||
? intersectingShip(
|
||||
ships(),
|
||||
shipProps(ships(), gameProps.mode, {
|
||||
x: props.x,
|
||||
y: props.y,
|
||||
orientation: targetPreview().orientation,
|
||||
}),
|
||||
true,
|
||||
).score < 2
|
||||
: true),
|
||||
})
|
||||
}
|
||||
|
||||
function BorderTiles() {
|
||||
const sessionProps = useSession()
|
||||
|
||||
const tilesProperties: TilesType[] = []
|
||||
|
||||
|
@ -97,47 +158,8 @@ function BorderTiles() {
|
|||
<div
|
||||
class={props.className}
|
||||
style={{ "--x": props.x, "--y": props.y }}
|
||||
onClick={() => {
|
||||
const sIndex = selfIndex()
|
||||
if (sIndex === -1) return
|
||||
if (gameProps.gameState === "running") {
|
||||
settingTarget(props.isGameTile, props.x, props.y)
|
||||
} else if (gameProps.gameState === "starting") {
|
||||
const { index } = intersectingShip(ships(), {
|
||||
...mouseCursor(),
|
||||
size: 1,
|
||||
variant: 0,
|
||||
orientation: "h",
|
||||
})
|
||||
if (typeof index === "undefined")
|
||||
settingTarget(props.isGameTile, props.x, props.y)
|
||||
else {
|
||||
const ship = ships()[index]
|
||||
setGameProps("mode", ship.size - 2)
|
||||
removeShip(ship, sIndex)
|
||||
setMouseCursor((e) => ({ ...e, shouldShow: true }))
|
||||
}
|
||||
}
|
||||
}}
|
||||
onMouseEnter={() =>
|
||||
setMouseCursor({
|
||||
x: props.x,
|
||||
y: props.y,
|
||||
shouldShow:
|
||||
props.isGameTile &&
|
||||
(gameProps.gameState === "starting"
|
||||
? intersectingShip(
|
||||
ships(),
|
||||
shipProps(ships(), gameProps.mode, {
|
||||
x: props.x,
|
||||
y: props.y,
|
||||
orientation: targetPreview().orientation,
|
||||
}),
|
||||
true,
|
||||
).score < 2
|
||||
: true),
|
||||
})
|
||||
}
|
||||
onClick={() => onClick(props, sessionProps)}
|
||||
onMouseEnter={() => onMouseEnter(props, sessionProps)}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
|
|
|
@ -44,7 +44,7 @@ import Item from "./Item"
|
|||
|
||||
function EventBar(props: { clear: () => void }) {
|
||||
const { shouldHide, setShouldHide, setEnable, color } = useDrawProps
|
||||
const { selfIndex, isActiveIndex, selfUser, ships } = useSession()
|
||||
const { selfIndex, selfIsActiveIndex, selfUser, ships } = useSession()
|
||||
const navigator = useNavigate()
|
||||
|
||||
const items = (): EventBarModes => ({
|
||||
|
@ -260,7 +260,7 @@ function EventBar(props: { clear: () => void }) {
|
|||
// if (gameProps.gameState !== "running") return
|
||||
|
||||
// const toastId = "otherPlayer"
|
||||
// if (isActiveIndex) toast.dismiss(toastId)
|
||||
// if (selfIsActiveIndex) toast.dismiss(toastId)
|
||||
// else
|
||||
// toast.info("Waiting for other player...", {
|
||||
// toastId,
|
||||
|
@ -299,7 +299,7 @@ function EventBar(props: { clear: () => void }) {
|
|||
<For each={items()[gameProps.menu]}>
|
||||
{(e, i) => (
|
||||
<Show
|
||||
when={isActiveIndex() || gameProps.menu !== "main" || i() !== 1}
|
||||
when={selfIsActiveIndex() || gameProps.menu !== "main" || i() !== 1}
|
||||
>
|
||||
<Item {...e} />
|
||||
</Show>
|
||||
|
@ -319,12 +319,12 @@ function EventBar(props: { clear: () => void }) {
|
|||
gameProps.mode >= 0 &&
|
||||
target().show,
|
||||
callback: () => {
|
||||
const i = selfIndex()
|
||||
if (i === -1) return
|
||||
const sIndex = selfIndex()
|
||||
if (!sIndex) return
|
||||
switch (gameProps.gameState) {
|
||||
case "starting":
|
||||
const isReady = !users[i]?.isReady
|
||||
setIsReadyFor({ isReady, i })
|
||||
const isReady = !users[sIndex.i]?.isReady
|
||||
setIsReadyFor({ isReady, i: sIndex.i })
|
||||
socket.emit("isReady", isReady)
|
||||
break
|
||||
|
||||
|
|
|
@ -2,15 +2,16 @@ import { faBurst, faXmark } from "@fortawesome/pro-solid-svg-icons"
|
|||
import { For } from "solid-js"
|
||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
||||
import { useSession } from "~/hooks/useSession"
|
||||
import { compiledHits } from "~/lib/utils/helpers"
|
||||
import { Hit } from "../../interfaces/frontend"
|
||||
|
||||
function HitElems(props: { hits?: Hit[]; colorOverride?: string }) {
|
||||
const { activeUser } = useSession()
|
||||
const { activeIndex } = useSession()
|
||||
const hits = () => props?.hits
|
||||
const colorOverride = () => props?.colorOverride
|
||||
|
||||
return (
|
||||
<For each={hits() ?? activeUser()?.hits()}>
|
||||
<For each={hits() ?? compiledHits(activeIndex())}>
|
||||
{(props) => (
|
||||
<div class="hit-svg" style={{ "--x": props.x, "--y": props.y }}>
|
||||
<FontAwesomeIcon
|
||||
|
|
|
@ -18,7 +18,7 @@ function Ship(
|
|||
) {
|
||||
const { selfIndex } = useSession()
|
||||
const filename = () =>
|
||||
`ship_${selfIndex() > 0 ? "red" : "blue"}_${props.size}x_${
|
||||
`ship_${selfIndex()?.i === 1 ? "red" : "blue"}_${props.size}x_${
|
||||
props.variant
|
||||
}.gif`
|
||||
let canvasRef: HTMLCanvasElement
|
||||
|
|
|
@ -4,10 +4,10 @@ import { useSession } from "~/hooks/useSession"
|
|||
import Ship from "./Ship"
|
||||
|
||||
function Ships() {
|
||||
const { isActiveIndex, selfUser } = useSession()
|
||||
const { selfIsActiveIndex, selfUser } = useSession()
|
||||
|
||||
return (
|
||||
<Show when={gameProps.gameState !== "running" || !isActiveIndex()}>
|
||||
<Show when={gameProps.gameState !== "running" || !selfIsActiveIndex()}>
|
||||
<For each={selfUser()?.ships}>{(props) => <Ship {...props} />}</For>
|
||||
</Show>
|
||||
)
|
||||
|
|
|
@ -24,7 +24,7 @@ function Targets() {
|
|||
each={composeTargetTiles(
|
||||
target(),
|
||||
gameProps.mode,
|
||||
compiledHits(activeIndex() === 0 ? 1 : 0),
|
||||
compiledHits(activeIndex()),
|
||||
)}
|
||||
>
|
||||
{(props) => <GamefieldPointer {...props} />}
|
||||
|
@ -33,7 +33,7 @@ function Targets() {
|
|||
each={composeTargetTiles(
|
||||
targetPreview(),
|
||||
gameProps.mode,
|
||||
compiledHits(activeIndex() === 0 ? 1 : 0),
|
||||
compiledHits(activeIndex()),
|
||||
)}
|
||||
>
|
||||
{(props) => <GamefieldPointer {...props} preview />}
|
||||
|
|
|
@ -103,17 +103,18 @@ function LobbyFrame(props: { openSettings: () => void }) {
|
|||
</p>
|
||||
}
|
||||
>
|
||||
<>
|
||||
<Player src="player_blue.png" i={0} userId={session()?.user?.id} />
|
||||
<p class="font-farro m-4 text-6xl font-semibold">VS</p>
|
||||
{users[1] ? (
|
||||
<Player src="player_red.png" i={1} userId={session()?.user?.id} />
|
||||
) : (
|
||||
<Show
|
||||
when={users[1]}
|
||||
fallback={
|
||||
<p class="font-farro w-96 text-center text-4xl font-medium">
|
||||
<WithDots>Warte auf Spieler 2</WithDots>
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Player src="player_red.png" i={1} userId={session()?.user?.id} />
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="flex items-center justify-around border-t-2 border-slate-900 p-4">
|
||||
|
|
|
@ -74,25 +74,25 @@ function Player(props: { src: string; i: 0 | 1; userId?: string }) {
|
|||
<Button
|
||||
type={
|
||||
player()?.isConnected
|
||||
? player()?.isReady
|
||||
? users[props.i]?.isReady
|
||||
? "green"
|
||||
: "orange"
|
||||
: "gray"
|
||||
}
|
||||
latching
|
||||
isLatched={player()?.isReady}
|
||||
isLatched={users[props.i]?.isReady}
|
||||
onClick={() => {
|
||||
if (!player()) return
|
||||
socket.emit("isReady", !player()?.isReady)
|
||||
socket.emit("isReady", !users[props.i]?.isReady)
|
||||
setIsReadyFor({
|
||||
i: props.i,
|
||||
isReady: !player()?.isReady,
|
||||
isReady: !users[props.i]?.isReady,
|
||||
})
|
||||
}}
|
||||
disabled={!primary()}
|
||||
>
|
||||
Ready
|
||||
{player()?.isReady && player()?.isConnected ? (
|
||||
{users[props.i]?.isReady && player()?.isConnected ? (
|
||||
<FontAwesomeIcon icon={faCheck} class="ml-4 w-12" />
|
||||
) : primary() ? (
|
||||
<FontAwesomeIcon
|
||||
|
|
|
@ -5,25 +5,11 @@ import {
|
|||
import classNames from "classnames"
|
||||
import { JSX } from "solid-js"
|
||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
||||
import {
|
||||
allowChat,
|
||||
allowMarkDraw,
|
||||
allowSpecials,
|
||||
allowSpectators,
|
||||
setGameSetting,
|
||||
} from "~/hooks/useGameProps"
|
||||
import { gameProps, setGameSetting } from "~/hooks/useGameProps"
|
||||
import { GameSettingKeys } from "../../../interfaces/frontend"
|
||||
|
||||
function Setting(props: { children: JSX.Element; key: GameSettingKeys }) {
|
||||
const state = () => {
|
||||
const gameProps = {
|
||||
allowChat,
|
||||
allowMarkDraw,
|
||||
allowSpecials,
|
||||
allowSpectators,
|
||||
}
|
||||
return gameProps[props.key]()
|
||||
}
|
||||
const state = () => gameProps[props.key]
|
||||
|
||||
return (
|
||||
<label class="flex items-center justify-between" for={props.key}>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { socket } from "~/lib/socket"
|
|||
import { GamePropsSchema, GameState } from "~/lib/zodSchemas"
|
||||
// import { toast } from "react-toastify"
|
||||
import { createSignal } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { createStore, produce } from "solid-js/store"
|
||||
import { getPayloadFromProps } from "~/lib/getPayloadFromProps"
|
||||
import { getPayloadwithChecksum } from "~/lib/getPayloadwithChecksum"
|
||||
import {
|
||||
|
@ -16,6 +16,7 @@ import {
|
|||
GameSettings,
|
||||
MouseCursor,
|
||||
MoveDispatchProps,
|
||||
Players,
|
||||
ShipProps,
|
||||
Target,
|
||||
TargetPreview,
|
||||
|
@ -53,7 +54,43 @@ export const [gameProps, setGameProps] =
|
|||
createStore<GameProps>(initialGameProps)
|
||||
|
||||
const initialUsers = { 0: null, 1: null }
|
||||
export const [users, setUsers] = createStore<Users>(initialUsers)
|
||||
export const [users, setUsersStore] = createStore<Users>(initialUsers)
|
||||
|
||||
export function setUsers(
|
||||
i: 0 | 1,
|
||||
userInput: Players | Partial<NonNullable<User>>,
|
||||
): void {
|
||||
setUsersStore(i, (state) => {
|
||||
if (0 in userInput) {
|
||||
const user = userInput[i]
|
||||
if (!user && state) return null
|
||||
else if (!state)
|
||||
return {
|
||||
...user,
|
||||
isReady: false,
|
||||
isConnected: false,
|
||||
}
|
||||
else
|
||||
return produce<NonNullable<typeof state>>((u) => {
|
||||
Object.assign(u, user)
|
||||
})(state)
|
||||
} else {
|
||||
const user = userInput
|
||||
if (!state) {
|
||||
console.error(
|
||||
"Everything is fine! Still, this was unexpected and should probably be fixed. 😁",
|
||||
userInput,
|
||||
state,
|
||||
)
|
||||
console.trace(userInput)
|
||||
return null
|
||||
}
|
||||
return produce<NonNullable<typeof state>>((u) => {
|
||||
Object.assign(u, user)
|
||||
})(state)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const [target, setTarget] = createSignal<Target>(initlialTarget)
|
||||
export const [targetPreview, setTargetPreview] = createSignal<TargetPreview>(
|
||||
|
@ -63,28 +100,34 @@ export const [mouseCursor, setMouseCursor] =
|
|||
createSignal<MouseCursor>(initlialMouseCursor)
|
||||
|
||||
export function DispatchMove(move: MoveDispatchProps, index: 0 | 1) {
|
||||
setUsers(index, "moves", (e) => [...e, move])
|
||||
setUsers(index, {
|
||||
moves: [...(users[index]?.moves ?? []), move],
|
||||
isReady: false,
|
||||
})
|
||||
}
|
||||
|
||||
export function setShips(ships: ShipProps[], index: 0 | 1) {
|
||||
setUsers(index, "ships", ships)
|
||||
setUsers(index, { ships })
|
||||
}
|
||||
|
||||
export function removeShip({ size, variant, x, y }: ShipProps, index: 0 | 1) {
|
||||
setUsers(index, "ships", (ships) => {
|
||||
const indexToRemove = ships.findIndex(
|
||||
const ships = users[index]?.ships ?? []
|
||||
const indexToRemove = users[index]?.ships.findIndex(
|
||||
(ship) =>
|
||||
ship.size === size &&
|
||||
ship.variant === variant &&
|
||||
ship.x === x &&
|
||||
ship.y === y,
|
||||
)
|
||||
|
||||
return ships.filter((_, i) => i !== indexToRemove)
|
||||
setUsers(index, {
|
||||
ships: ships.filter((_, i) => i !== indexToRemove),
|
||||
})
|
||||
}
|
||||
|
||||
export function setPlayer(newUsers: Users): string | null {
|
||||
export function setPlayer(newUsers: Players): string | null {
|
||||
let hash: string | null = null
|
||||
setUsers(newUsers)
|
||||
setUsers(0, newUsers)
|
||||
setUsers(1, newUsers)
|
||||
const body = getPayloadwithChecksum(getPayloadFromProps())
|
||||
if (!body.hash) {
|
||||
console.log("Something is wrong... ")
|
||||
|
@ -98,6 +141,7 @@ export function setPlayer(newUsers: Users): string | null {
|
|||
setGameProps("hash", hash)
|
||||
return hash
|
||||
}
|
||||
|
||||
export function setSetting(newSettings: GameSettings): string | null {
|
||||
let hash: string | null = null
|
||||
setGameProps("allowChat", (e) => newSettings.allowChat ?? e)
|
||||
|
@ -152,21 +196,11 @@ export function full(newProps: GamePropsSchema) {
|
|||
allowSpecials: newProps.payload.game?.allowSpecials ?? false,
|
||||
allowSpectators: newProps.payload.game?.allowSpectators ?? false,
|
||||
})
|
||||
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] })
|
||||
setUsers(0, newProps.payload.users)
|
||||
setUsers(1, newProps.payload.users)
|
||||
}
|
||||
}
|
||||
|
||||
export function leave(cb: () => void) {
|
||||
socket.emit("leave", (ack) => {
|
||||
if (!ack) {
|
||||
|
@ -176,24 +210,28 @@ export function leave(cb: () => void) {
|
|||
cb()
|
||||
})
|
||||
}
|
||||
|
||||
export function setIsReadyFor({ i, isReady }: { i: 0 | 1; isReady: boolean }) {
|
||||
setUsers(i, (e) => ({ ...e, isReady, isConnected: true }))
|
||||
setUsers(i, {
|
||||
isReady: isReady,
|
||||
isConnected: true,
|
||||
})
|
||||
}
|
||||
|
||||
export function newGameState(newState: GameState) {
|
||||
setGameProps("gameState", newState)
|
||||
setUsers(0, (e) => (e && e.isReady ? { isReady: false } : e))
|
||||
setUsers(1, (e) => (e && e.isReady ? { isReady: false } : e))
|
||||
setUsers(0, { isReady: false })
|
||||
setUsers(1, { isReady: false })
|
||||
}
|
||||
export function setIsConnectedFor({
|
||||
i,
|
||||
isConnected,
|
||||
}: {
|
||||
i: 0 | 1
|
||||
isConnected: boolean
|
||||
}) {
|
||||
setUsers(i, "isConnected", isConnected)
|
||||
if (isConnected) return
|
||||
setUsers(i, "isReady", false)
|
||||
|
||||
export function setIsConnectedFor(props: { i: 0 | 1; isConnected: boolean }) {
|
||||
setUsers(props.i, {
|
||||
isConnected: props.isConnected,
|
||||
})
|
||||
if (props.isConnected) return
|
||||
setUsers(props.i, {
|
||||
isReady: false,
|
||||
})
|
||||
}
|
||||
|
||||
export function reset() {
|
||||
|
@ -201,5 +239,5 @@ export function reset() {
|
|||
setTarget(initlialTarget)
|
||||
setTargetPreview(initlialTargetPreview)
|
||||
setMouseCursor(initlialMouseCursor)
|
||||
setUsers(initialUsers)
|
||||
setUsersStore(initialUsers)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Session } from "@auth/core/types"
|
||||
import objectHash from "object-hash"
|
||||
import stringify from "json-stable-stringify"
|
||||
import {
|
||||
JSX,
|
||||
createContext,
|
||||
|
@ -13,14 +13,14 @@ import { gameProps, setGameProps, users } from "./useGameProps"
|
|||
|
||||
const [state, setState] = createSignal<Session | null | undefined>(undefined)
|
||||
|
||||
const selfIndex = () => {
|
||||
function selfIndex(): { i: 0 | 1 } | null {
|
||||
switch (state()?.user?.id) {
|
||||
case users[0]?.id:
|
||||
return 0
|
||||
return { i: 0 }
|
||||
case users[1]?.id:
|
||||
return 1
|
||||
return { i: 1 }
|
||||
default:
|
||||
return -1
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,23 +31,22 @@ const activeIndex = () => {
|
|||
return l1 > l2 ? 1 : 0
|
||||
}
|
||||
|
||||
const isActiveIndex = () => {
|
||||
const sI = selfIndex()
|
||||
return sI >= 0 && activeIndex() === sI
|
||||
const selfIsActiveIndex = () => {
|
||||
const sIndex = selfIndex()
|
||||
return !!sIndex && activeIndex() === sIndex.i
|
||||
}
|
||||
|
||||
const selfUser = () => {
|
||||
const i = selfIndex()
|
||||
if (i === -1) return null
|
||||
return users[i]
|
||||
const sIndex = selfIndex()
|
||||
return sIndex ? users[sIndex.i] : null
|
||||
}
|
||||
|
||||
/**
|
||||
* It should be the opposite of `activeIndex`.
|
||||
*
|
||||
* This is because `activeIndex` is attacking the `activeUser`.
|
||||
* This is because `activeIndex` is attacking the `enemyUser`.
|
||||
*/
|
||||
const activeUser = () => users[activeIndex() === 0 ? 1 : 0]
|
||||
const enemyUser = () => users[activeIndex() === 0 ? 1 : 0]
|
||||
|
||||
const ships = () => selfUser()?.ships ?? []
|
||||
|
||||
|
@ -55,9 +54,9 @@ const contextValue = {
|
|||
session: state,
|
||||
selfIndex,
|
||||
activeIndex,
|
||||
isActiveIndex,
|
||||
selfIsActiveIndex,
|
||||
selfUser,
|
||||
activeUser,
|
||||
activeUser: enemyUser,
|
||||
ships,
|
||||
}
|
||||
export const SessionCtx = createContext(contextValue)
|
||||
|
@ -77,7 +76,7 @@ export function SessionProvider(props: { children: JSX.Element }) {
|
|||
|
||||
createEffect(() => {
|
||||
const session = data()
|
||||
const hashDiff = objectHash(session ?? null) !== objectHash(state() ?? null)
|
||||
const hashDiff = stringify(session) !== stringify(state())
|
||||
if (session === undefined || data.loading || !hashDiff) return
|
||||
console.log("Session updated.")
|
||||
setState(session)
|
||||
|
@ -85,7 +84,9 @@ export function SessionProvider(props: { children: JSX.Element }) {
|
|||
|
||||
createEffect(() => {
|
||||
if (gameProps.gameState !== "running") return
|
||||
if (activeIndex() === selfIndex()) {
|
||||
|
||||
const sIndex = selfIndex()
|
||||
if (activeIndex() === sIndex?.i) {
|
||||
setGameProps("menu", "moves")
|
||||
setGameProps("mode", 0)
|
||||
} else {
|
||||
|
|
|
@ -3,7 +3,6 @@ import status from "http-status"
|
|||
import { createEffect, createSignal, onCleanup } from "solid-js"
|
||||
import { useNavigate } from "solid-start"
|
||||
import { socket } from "~/lib/socket"
|
||||
import { frontendUsers } from "~/lib/utils/helpers"
|
||||
import { GamePropsSchema, GameState } from "~/lib/zodSchemas"
|
||||
import { isAuthenticated } from "~/routes/start"
|
||||
import { GameSettings, PlayerEvent } from "../interfaces/frontend"
|
||||
|
@ -28,17 +27,18 @@ function useSocket() {
|
|||
const navigator = useNavigate()
|
||||
|
||||
const isConnected = () => {
|
||||
const i = selfIndex()
|
||||
return i !== -1
|
||||
? users[i]?.isConnected && isConnectedState()
|
||||
const sIndex = selfIndex()
|
||||
return sIndex
|
||||
? users[sIndex.i]?.isConnected && isConnectedState()
|
||||
: isConnectedState()
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
const i = selfIndex()
|
||||
if (i === -1) return
|
||||
const sIndex = selfIndex()
|
||||
if (!sIndex) return
|
||||
if (!users[sIndex.i]) return
|
||||
setIsConnectedFor({
|
||||
i,
|
||||
i: sIndex.i,
|
||||
isConnected: isConnectedState(),
|
||||
})
|
||||
})
|
||||
|
@ -68,7 +68,17 @@ function useSocket() {
|
|||
const playerEvent = (event: PlayerEvent) => {
|
||||
const { type, i } = event
|
||||
let message: string
|
||||
console.log("playerEvent", type)
|
||||
if (type !== "disconnect") {
|
||||
const { hash } = event
|
||||
const newHash = setPlayer(event.users)
|
||||
if (newHash && newHash !== hash) {
|
||||
console.log("hash", hash, newHash)
|
||||
socket.emit("update", (body) => {
|
||||
console.log("Update is needed after", type)
|
||||
full(body)
|
||||
})
|
||||
}
|
||||
}
|
||||
switch (type) {
|
||||
case "disconnect":
|
||||
setIsConnectedFor({
|
||||
|
@ -87,9 +97,8 @@ function useSocket() {
|
|||
i,
|
||||
isConnected: true,
|
||||
})
|
||||
const index = selfIndex()
|
||||
if (index !== -1)
|
||||
socket.emit("isReady", users[index]?.isReady ?? false)
|
||||
const sIndex = selfIndex()
|
||||
if (sIndex) socket.emit("isReady", users[sIndex.i]?.isReady ?? false)
|
||||
message = "Player has joined the lobby."
|
||||
break
|
||||
|
||||
|
@ -99,19 +108,13 @@ function useSocket() {
|
|||
}
|
||||
// toast.info(message, { toastId: message })
|
||||
console.log(message)
|
||||
if (type === "disconnect") return
|
||||
|
||||
const { hash } = event
|
||||
const newHash = setPlayer(frontendUsers(event.users))
|
||||
if (!newHash || newHash === hash) return
|
||||
console.log("hash", hash, newHash)
|
||||
socket.emit("update", (body) => {
|
||||
console.log("Update is needed after ", type)
|
||||
full(body)
|
||||
})
|
||||
}
|
||||
|
||||
const setGameState = (state: GameState) => setGameProps("gameState", state)
|
||||
const setGameState = (state: GameState) => {
|
||||
setGameProps("gameState", state)
|
||||
setIsReadyFor({ i: 0, isReady: false })
|
||||
setIsReadyFor({ i: 1, isReady: false })
|
||||
}
|
||||
|
||||
const gameSetting = (newSettings: GameSettings, hash: string) => {
|
||||
const newHash = setSetting(newSettings)
|
||||
|
|
|
@ -55,14 +55,9 @@ interface InterServerEvents {
|
|||
}
|
||||
|
||||
interface SocketData {
|
||||
props: {
|
||||
userId: string
|
||||
gameId: string
|
||||
index: 0 | 1
|
||||
}
|
||||
user: Session["user"]
|
||||
gameId: string
|
||||
index: 0 | 1
|
||||
index: { i: 0 | 1 } | null
|
||||
}
|
||||
|
||||
export type sServer = Server<
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import hash from "object-hash"
|
||||
import stringify from "json-stable-stringify"
|
||||
import objectHash from "object-hash"
|
||||
import { GamePropsSchema } from "./zodSchemas"
|
||||
|
||||
export const getPayloadwithChecksum = (
|
||||
payload: GamePropsSchema["payload"],
|
||||
): GamePropsSchema => ({ payload, hash: hash(payload) })
|
||||
): GamePropsSchema => ({ payload, hash: objectHash(stringify(payload)) })
|
||||
|
|
|
@ -4,14 +4,12 @@ import type {
|
|||
Hit,
|
||||
IndexedPosition,
|
||||
Mode,
|
||||
Players,
|
||||
PointerProps,
|
||||
Position,
|
||||
ShipProps,
|
||||
Target,
|
||||
TargetList,
|
||||
TargetPreview,
|
||||
User,
|
||||
} from "../../interfaces/frontend"
|
||||
import { MoveType, Orientation } from "../zodSchemas"
|
||||
|
||||
|
@ -227,14 +225,14 @@ export function intersectingShip(
|
|||
|
||||
export function compiledHits(i: 0 | 1) {
|
||||
return (
|
||||
users[i === 0 ? 1 : 0]?.moves.reduce((hits, move) => {
|
||||
users[i]?.moves.reduce((hits, move) => {
|
||||
const list = targetList(move, move.type)
|
||||
return move.type === MoveType.Enum.radar
|
||||
? hits
|
||||
: [
|
||||
...hits,
|
||||
...list.map(({ x, y }) => ({
|
||||
hit: !!intersectingShip(users[i]?.ships ?? [], {
|
||||
hit: !!intersectingShip(users[i === 0 ? 1 : 0]?.ships ?? [], {
|
||||
...move,
|
||||
size: 1,
|
||||
variant: 0,
|
||||
|
@ -246,16 +244,3 @@ export function compiledHits(i: 0 | 1) {
|
|||
}, [] 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] }
|
||||
}
|
||||
|
|
|
@ -104,22 +104,9 @@ export function composeBody(
|
|||
...props,
|
||||
...user,
|
||||
}))
|
||||
const emptyUser = {
|
||||
id: "",
|
||||
name: "",
|
||||
chats: [],
|
||||
moves: [],
|
||||
ships: [],
|
||||
}
|
||||
const composedUsers = {
|
||||
0: mappedUsers.find((e) => e.index === 0) ?? {
|
||||
index: 0,
|
||||
...emptyUser,
|
||||
},
|
||||
1: mappedUsers.find((e) => e.index === 1) ?? {
|
||||
index: 1,
|
||||
...emptyUser,
|
||||
},
|
||||
0: mappedUsers.find((e) => e.index === 0) ?? null,
|
||||
1: mappedUsers.find((e) => e.index === 1) ?? null,
|
||||
}
|
||||
const payload = {
|
||||
game: game,
|
||||
|
|
|
@ -65,12 +65,12 @@ export async function GET({
|
|||
const { payload, hash } = composeBody(game)
|
||||
const index = payload.users[0]?.id === socket.data.user?.id ? 0 : 1
|
||||
if (index !== 0 && index !== 1) return next(new Error(status["401"]))
|
||||
socket.data.index = index
|
||||
socket.data.index = { i: index }
|
||||
socket.data.gameId = game.id
|
||||
socket.join(game.id)
|
||||
socket.to(game.id).emit("playerEvent", {
|
||||
type: "connect",
|
||||
i: socket.data.index,
|
||||
i: socket.data.index.i,
|
||||
users: payload.users,
|
||||
hash,
|
||||
})
|
||||
|
@ -93,9 +93,9 @@ export async function GET({
|
|||
|
||||
socket.on("update", async (cb) => {
|
||||
const game = await getGameById(socket.data.gameId ?? "")
|
||||
if (!game) return
|
||||
if (socket.data.index === 1 && game.users.length === 1)
|
||||
socket.data.index = 0
|
||||
if (!game || !socket.data.index) return
|
||||
if (socket.data.index.i === 1 && game.users.length === 1)
|
||||
socket.data.index.i = 0
|
||||
const body = composeBody(game)
|
||||
cb(body)
|
||||
})
|
||||
|
@ -122,6 +122,7 @@ export async function GET({
|
|||
socket.on("ping", (callback) => callback())
|
||||
|
||||
socket.on("leave", async (cb) => {
|
||||
if (!socket.data.index) return
|
||||
const user_Game = (
|
||||
await db
|
||||
.delete(user_games)
|
||||
|
@ -170,7 +171,7 @@ export async function GET({
|
|||
const { payload, hash } = body
|
||||
socket.to(socket.data.gameId).emit("playerEvent", {
|
||||
type: "leave",
|
||||
i: socket.data.index,
|
||||
i: socket.data.index.i,
|
||||
users: payload.users,
|
||||
hash,
|
||||
})
|
||||
|
@ -183,13 +184,10 @@ export async function GET({
|
|||
})
|
||||
|
||||
socket.on("isReady", async (isReady) => {
|
||||
if (socket.data.index === undefined || !socket.data.gameId) return
|
||||
if (!socket.data.index || !socket.data.gameId) return
|
||||
socket
|
||||
.to(socket.data.gameId)
|
||||
.emit("isReady", { i: socket.data.index, isReady })
|
||||
socket
|
||||
.to(socket.data.gameId)
|
||||
.emit("isConnected", { i: socket.data.index, isConnected: true })
|
||||
.emit("isReady", { i: socket.data.index.i, isReady })
|
||||
})
|
||||
|
||||
socket.on("canvas-state", (state) => {
|
||||
|
@ -197,7 +195,7 @@ export async function GET({
|
|||
console.log("received canvas state")
|
||||
socket
|
||||
.to(socket.data.gameId)
|
||||
.emit("canvas-state-from-server", state, socket.data.index)
|
||||
.emit("canvas-state-from-server", state, socket.data.index.i)
|
||||
})
|
||||
|
||||
socket.on("draw-line", ({ prevPoint, currentPoint, color }) => {
|
||||
|
@ -207,7 +205,7 @@ export async function GET({
|
|||
.emit(
|
||||
"draw-line",
|
||||
{ prevPoint, currentPoint, color },
|
||||
socket.data.index,
|
||||
socket.data.index.i,
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -217,7 +215,7 @@ export async function GET({
|
|||
})
|
||||
|
||||
socket.on("gameState", async (newState) => {
|
||||
if (socket.data.index !== 0 || !socket.data.gameId) return
|
||||
if (socket.data.index?.i !== 0 || !socket.data.gameId) return
|
||||
await db
|
||||
.update(games)
|
||||
.set({
|
||||
|
@ -228,11 +226,7 @@ export async function GET({
|
|||
})
|
||||
|
||||
socket.on("ships", async (shipsData) => {
|
||||
if (
|
||||
!socket.data.gameId ||
|
||||
!socket.data.user?.id ||
|
||||
typeof socket.data.index === "undefined"
|
||||
)
|
||||
if (!socket.data.gameId || !socket.data.user?.id || !socket.data.index)
|
||||
return
|
||||
|
||||
const user_Game = await db.query.user_games.findFirst({
|
||||
|
@ -254,15 +248,11 @@ export async function GET({
|
|||
|
||||
socket
|
||||
.to(socket.data.gameId)
|
||||
.emit("ships", shipsData, socket.data.index)
|
||||
.emit("ships", shipsData, socket.data.index.i)
|
||||
})
|
||||
|
||||
socket.on("dispatchMove", async (props) => {
|
||||
if (
|
||||
!socket.data.gameId ||
|
||||
!socket.data.user?.id ||
|
||||
typeof socket.data.index === "undefined"
|
||||
)
|
||||
if (!socket.data.gameId || !socket.data.user?.id || !socket.data.index)
|
||||
return
|
||||
|
||||
const user_Game = await db.query.user_games.findFirst({
|
||||
|
@ -280,7 +270,11 @@ export async function GET({
|
|||
.values({ ...props, id: createId(), user_game_id: user_Game.id })
|
||||
.returning()
|
||||
|
||||
io.to(socket.data.gameId).emit("dispatchMove", props, socket.data.index)
|
||||
io.to(socket.data.gameId).emit(
|
||||
"dispatchMove",
|
||||
props,
|
||||
socket.data.index.i,
|
||||
)
|
||||
})
|
||||
|
||||
socket.on("disconnecting", async () => {
|
||||
|
@ -289,10 +283,10 @@ export async function GET({
|
|||
["debug"],
|
||||
request,
|
||||
)
|
||||
if (!socket.data.gameId) return
|
||||
if (!socket.data.gameId || !socket.data.index) return
|
||||
socket.to(socket.data.gameId).emit("playerEvent", {
|
||||
type: "disconnect",
|
||||
i: socket.data.index,
|
||||
i: socket.data.index.i,
|
||||
})
|
||||
})
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue