user states improvements and fixes

This commit is contained in:
aronmal 2023-09-08 19:55:33 +02:00
parent 252f6f6028
commit efcb61b1ed
Signed by: aronmal
GPG key ID: 816B7707426FC612
19 changed files with 309 additions and 273 deletions

View file

@ -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",

View file

@ -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'}

View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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>
)

View file

@ -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 />}

View file

@ -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">

View file

@ -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

View file

@ -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}>

View file

@ -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)
}

View file

@ -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 {

View file

@ -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)

View file

@ -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<

View file

@ -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)) })

View file

@ -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] }
}

View file

@ -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,

View file

@ -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,
})
})