Further fixes for SolidJS
This commit is contained in:
parent
db7fb9213e
commit
89b79fa245
42 changed files with 1009 additions and 913 deletions
|
@ -6,7 +6,8 @@
|
||||||
"build": "solid-start build",
|
"build": "solid-start build",
|
||||||
"lint": "eslint --fix \"**/*.{ts,tsx,js,jsx}\"",
|
"lint": "eslint --fix \"**/*.{ts,tsx,js,jsx}\"",
|
||||||
"push": "drizzle-kit push:pg",
|
"push": "drizzle-kit push:pg",
|
||||||
"test": "pnpm playwright test --ui"
|
"test": "pnpm playwright test --ui",
|
||||||
|
"typecheck": "tsc --noEmit --checkJs false --skipLibCheck"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
"drizzle-zod": "^0.5.0",
|
"drizzle-zod": "^0.5.0",
|
||||||
"http-status": "^1.6.2",
|
"http-status": "^1.6.2",
|
||||||
"nodemailer": "6.9.4",
|
"nodemailer": "6.9.4",
|
||||||
|
"object-hash": "^3.0.0",
|
||||||
"postgres": "^3.3.5",
|
"postgres": "^3.3.5",
|
||||||
"socket.io": "^4.7.2",
|
"socket.io": "^4.7.2",
|
||||||
"socket.io-client": "^4.7.2",
|
"socket.io-client": "^4.7.2",
|
||||||
|
@ -44,6 +46,7 @@
|
||||||
"@total-typescript/ts-reset": "^0.4.2",
|
"@total-typescript/ts-reset": "^0.4.2",
|
||||||
"@types/node": "^20.5.0",
|
"@types/node": "^20.5.0",
|
||||||
"@types/nodemailer": "^6.4.9",
|
"@types/nodemailer": "^6.4.9",
|
||||||
|
"@types/object-hash": "^3.0.3",
|
||||||
"@types/web-bluetooth": "^0.0.17",
|
"@types/web-bluetooth": "^0.0.17",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.4.0",
|
"@typescript-eslint/eslint-plugin": "^6.4.0",
|
||||||
"autoprefixer": "^10.4.15",
|
"autoprefixer": "^10.4.15",
|
||||||
|
|
11
leaky-ships/pnpm-lock.yaml
generated
11
leaky-ships/pnpm-lock.yaml
generated
|
@ -65,6 +65,9 @@ dependencies:
|
||||||
nodemailer:
|
nodemailer:
|
||||||
specifier: 6.9.4
|
specifier: 6.9.4
|
||||||
version: 6.9.4
|
version: 6.9.4
|
||||||
|
object-hash:
|
||||||
|
specifier: ^3.0.0
|
||||||
|
version: 3.0.0
|
||||||
postgres:
|
postgres:
|
||||||
specifier: ^3.3.5
|
specifier: ^3.3.5
|
||||||
version: 3.3.5
|
version: 3.3.5
|
||||||
|
@ -103,6 +106,9 @@ devDependencies:
|
||||||
'@types/nodemailer':
|
'@types/nodemailer':
|
||||||
specifier: ^6.4.9
|
specifier: ^6.4.9
|
||||||
version: 6.4.9
|
version: 6.4.9
|
||||||
|
'@types/object-hash':
|
||||||
|
specifier: ^3.0.3
|
||||||
|
version: 3.0.3
|
||||||
'@types/web-bluetooth':
|
'@types/web-bluetooth':
|
||||||
specifier: ^0.0.17
|
specifier: ^0.0.17
|
||||||
version: 0.0.17
|
version: 0.0.17
|
||||||
|
@ -2075,6 +2081,10 @@ packages:
|
||||||
'@types/node': 18.17.5
|
'@types/node': 18.17.5
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/object-hash@3.0.3:
|
||||||
|
resolution: {integrity: sha512-Mb0SDIhjhBAz4/rDNU0cYcQR4lSJIwy+kFlm0whXLkx+o0pXwEszwyrWD6gXWumxVbAS6XZ9gXK82LR+Uk+cKQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/resolve@1.20.2:
|
/@types/resolve@1.20.2:
|
||||||
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
|
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
|
||||||
|
|
||||||
|
@ -4257,7 +4267,6 @@ packages:
|
||||||
/object-hash@3.0.0:
|
/object-hash@3.0.0:
|
||||||
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
|
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/object-inspect@1.12.3:
|
/object-inspect@1.12.3:
|
||||||
resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==}
|
resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==}
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
import { For } from "solid-js"
|
import { For } from "solid-js"
|
||||||
import { useGameProps } from "~/hooks/useGameProps"
|
import {
|
||||||
import useIndex from "~/hooks/useIndex"
|
gameState,
|
||||||
import useShips from "~/hooks/useShips"
|
mode,
|
||||||
|
mouseCursor,
|
||||||
|
removeShip,
|
||||||
|
setMode,
|
||||||
|
setMouseCursor,
|
||||||
|
setShips,
|
||||||
|
setTarget,
|
||||||
|
targetPreview,
|
||||||
|
} from "~/hooks/useGameProps"
|
||||||
|
import { useSession } from "~/hooks/useSession"
|
||||||
import {
|
import {
|
||||||
borderCN,
|
borderCN,
|
||||||
cornerCN,
|
cornerCN,
|
||||||
|
@ -23,40 +32,36 @@ type TilesType = {
|
||||||
}
|
}
|
||||||
|
|
||||||
function BorderTiles() {
|
function BorderTiles() {
|
||||||
const { activeUser } = useIndex()
|
const { selfIndex, activeUser, ships } = useSession()
|
||||||
const {
|
|
||||||
payload,
|
|
||||||
mode,
|
|
||||||
targetPreview,
|
|
||||||
mouseCursor,
|
|
||||||
setTarget,
|
|
||||||
setMouseCursor,
|
|
||||||
} = useGameProps()
|
|
||||||
const { ships, setShips, removeShip } = useShips()
|
|
||||||
|
|
||||||
const settingTarget = (isGameTile: boolean, x: number, y: number) => {
|
const settingTarget = (isGameTile: boolean, x: number, y: number) => {
|
||||||
if (payload?.game?.state === "running") {
|
if (gameState() === "running") {
|
||||||
const list = targetList(targetPreview, mode)
|
const list = targetList(targetPreview(), mode())
|
||||||
if (
|
if (
|
||||||
!isGameTile ||
|
!isGameTile ||
|
||||||
!list.filter(({ x, y }) => !isAlreadyHit(x, y, activeUser?.hits ?? []))
|
!list.filter(
|
||||||
.length
|
({ x, y }) => !isAlreadyHit(x, y, activeUser()?.hits() ?? []),
|
||||||
|
).length
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if (!overlapsWithAnyBorder(targetPreview, mode))
|
if (!overlapsWithAnyBorder(targetPreview(), mode()))
|
||||||
setTarget({
|
setTarget({
|
||||||
show: true,
|
show: true,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
orientation: targetPreview.orientation,
|
orientation: targetPreview().orientation,
|
||||||
})
|
})
|
||||||
} else if (
|
} else if (
|
||||||
payload?.game?.state === "starting" &&
|
gameState() === "starting" &&
|
||||||
targetPreview.show &&
|
targetPreview().show &&
|
||||||
!intersectingShip(ships(), shipProps(ships(), mode, targetPreview)).score
|
!intersectingShip(ships(), shipProps(ships(), mode(), targetPreview()))
|
||||||
|
.score
|
||||||
) {
|
) {
|
||||||
setMouseCursor((e) => ({ ...e, shouldShow: false }))
|
setMouseCursor((e) => ({ ...e, shouldShow: false }))
|
||||||
setShips([...ships(), shipProps(ships(), mode, targetPreview)])
|
setShips(
|
||||||
|
[...ships(), shipProps(ships(), mode(), targetPreview())],
|
||||||
|
selfIndex(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,11 +94,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 (payload?.game?.state === "running") {
|
if (gameState() === "running") {
|
||||||
settingTarget(props.isGameTile, props.x, props.y)
|
settingTarget(props.isGameTile, props.x, props.y)
|
||||||
} else if (payload?.game?.state === "starting") {
|
} else if (gameState() === "starting") {
|
||||||
const { index } = intersectingShip(ships(), {
|
const { index } = intersectingShip(ships(), {
|
||||||
...mouseCursor,
|
...mouseCursor(),
|
||||||
size: 1,
|
size: 1,
|
||||||
variant: 0,
|
variant: 0,
|
||||||
orientation: "h",
|
orientation: "h",
|
||||||
|
@ -102,8 +107,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]
|
||||||
useGameProps.setState({ mode: ship.size - 2 })
|
setMode(ship.size - 2)
|
||||||
removeShip(ship)
|
removeShip(ship, selfIndex())
|
||||||
setMouseCursor((e) => ({ ...e, shouldShow: true }))
|
setMouseCursor((e) => ({ ...e, shouldShow: true }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,13 +119,13 @@ function BorderTiles() {
|
||||||
y: props.y,
|
y: props.y,
|
||||||
shouldShow:
|
shouldShow:
|
||||||
props.isGameTile &&
|
props.isGameTile &&
|
||||||
(payload?.game?.state === "starting"
|
(gameState() === "starting"
|
||||||
? intersectingShip(
|
? intersectingShip(
|
||||||
ships(),
|
ships(),
|
||||||
shipProps(ships(), mode, {
|
shipProps(ships(), mode(), {
|
||||||
x: props.x,
|
x: props.x,
|
||||||
y: props.y,
|
y: props.y,
|
||||||
orientation: targetPreview.orientation,
|
orientation: targetPreview().orientation,
|
||||||
}),
|
}),
|
||||||
true,
|
true,
|
||||||
).score < 2
|
).score < 2
|
||||||
|
|
|
@ -23,52 +23,36 @@ import {
|
||||||
} from "@fortawesome/pro-solid-svg-icons"
|
} from "@fortawesome/pro-solid-svg-icons"
|
||||||
import { socket } from "~/lib/socket"
|
import { socket } from "~/lib/socket"
|
||||||
import { modes } from "~/lib/utils/helpers"
|
import { modes } from "~/lib/utils/helpers"
|
||||||
import { GamePropsSchema } from "~/lib/zodSchemas"
|
|
||||||
// import { Icons, toast } from "react-toastify"
|
// import { Icons, toast } from "react-toastify"
|
||||||
import { For, Show, createEffect } from "solid-js"
|
import { For, Show, createEffect } from "solid-js"
|
||||||
import { useNavigate } from "solid-start"
|
import { useNavigate } from "solid-start"
|
||||||
import { useDrawProps } from "~/hooks/useDrawProps"
|
import { useDrawProps } from "~/hooks/useDrawProps"
|
||||||
import { useGameProps } from "~/hooks/useGameProps"
|
import {
|
||||||
import useIndex from "~/hooks/useIndex"
|
allowChat,
|
||||||
import useShips from "~/hooks/useShips"
|
allowMarkDraw,
|
||||||
import { EventBarModes, GameSettings } from "../../interfaces/frontend"
|
allowSpecials,
|
||||||
|
allowSpectators,
|
||||||
|
gameState,
|
||||||
|
menu,
|
||||||
|
mode,
|
||||||
|
reset,
|
||||||
|
setGameSetting,
|
||||||
|
setIsReadyFor,
|
||||||
|
setMenu,
|
||||||
|
setMode,
|
||||||
|
setTarget,
|
||||||
|
setTargetPreview,
|
||||||
|
target,
|
||||||
|
users,
|
||||||
|
} from "~/hooks/useGameProps"
|
||||||
|
import { useSession } from "~/hooks/useSession"
|
||||||
|
import { EventBarModes } from "../../interfaces/frontend"
|
||||||
import Item from "./Item"
|
import Item from "./Item"
|
||||||
|
|
||||||
export function setGameSetting(
|
|
||||||
payload: GameSettings,
|
|
||||||
setSetting: (settings: GameSettings) => string | null,
|
|
||||||
full: (payload: GamePropsSchema) => void,
|
|
||||||
) {
|
|
||||||
return () => {
|
|
||||||
const hash = setSetting(payload)
|
|
||||||
socket.emit("gameSetting", payload, (newHash) => {
|
|
||||||
if (newHash === hash) return
|
|
||||||
console.log("hash", hash, newHash)
|
|
||||||
socket.emit("update", full)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function EventBar(props: { clear: () => void }) {
|
function EventBar(props: { clear: () => void }) {
|
||||||
const { shouldHide, color } = useDrawProps()
|
const { shouldHide, color } = useDrawProps()
|
||||||
const { selfIndex, isActiveIndex, selfUser } = useIndex()
|
const { selfIndex, isActiveIndex, selfUser, ships } = useSession()
|
||||||
const { ships } = useShips()
|
const navigator = useNavigate()
|
||||||
const navigate = useNavigate()
|
|
||||||
const {
|
|
||||||
payload,
|
|
||||||
userStates,
|
|
||||||
menu,
|
|
||||||
mode,
|
|
||||||
setSetting,
|
|
||||||
full,
|
|
||||||
target,
|
|
||||||
setTarget,
|
|
||||||
setTargetPreview,
|
|
||||||
setIsReady,
|
|
||||||
reset,
|
|
||||||
} = useGameProps()
|
|
||||||
const gameSetting = (payload: GameSettings) =>
|
|
||||||
setGameSetting(payload, setSetting, full)
|
|
||||||
|
|
||||||
const items = (): EventBarModes => ({
|
const items = (): EventBarModes => ({
|
||||||
main: [
|
main: [
|
||||||
|
@ -76,36 +60,36 @@ function EventBar(props: { clear: () => void }) {
|
||||||
icon: "burger-menu",
|
icon: "burger-menu",
|
||||||
text: "Menu",
|
text: "Menu",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
useGameProps.setState({ menu: "menu" })
|
setMenu("menu")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
payload?.game?.state === "running"
|
gameState() === "running"
|
||||||
? {
|
? {
|
||||||
icon: faSwords,
|
icon: faSwords,
|
||||||
text: "Attack",
|
text: "Attack",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
useGameProps.setState({ menu: "moves" })
|
setMenu("moves")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
icon: faShip,
|
icon: faShip,
|
||||||
text: "Ships",
|
text: "Ships",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
useGameProps.setState({ menu: "moves" })
|
setMenu("moves")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "pen",
|
icon: "pen",
|
||||||
text: "Draw",
|
text: "Draw",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
useGameProps.setState({ menu: "draw" })
|
setMenu("draw")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "gear",
|
icon: "gear",
|
||||||
text: "Settings",
|
text: "Settings",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
useGameProps.setState({ menu: "settings" })
|
setMenu("settings")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -115,47 +99,49 @@ function EventBar(props: { clear: () => void }) {
|
||||||
text: "Surrender",
|
text: "Surrender",
|
||||||
iconColor: "darkred",
|
iconColor: "darkred",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
useGameProps.setState({ menu: "surrender" })
|
setMenu("surrender")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
moves:
|
moves:
|
||||||
payload?.game?.state === "running"
|
gameState() === "running"
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
icon: "scope",
|
icon: "scope",
|
||||||
text: "Fire missile",
|
text: "Fire missile",
|
||||||
enabled: mode === 0,
|
enabled: mode() === 0,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
useGameProps.setState({ mode: 0 })
|
setMode(0)
|
||||||
setTarget((e) => ({ ...e, show: false }))
|
setTarget((t) => ({ ...t, show: false }))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "torpedo",
|
icon: "torpedo",
|
||||||
text: "Fire torpedo",
|
text: "Fire torpedo",
|
||||||
enabled: mode === 1 || mode === 2,
|
enabled: mode() === 1 || mode() === 2,
|
||||||
amount:
|
amount:
|
||||||
2 -
|
2 -
|
||||||
((selfUser?.moves ?? []).filter(
|
(selfUser()
|
||||||
(e) => e.type === "htorpedo" || e.type === "vtorpedo",
|
?.moves()
|
||||||
).length ?? 0),
|
.filter((e) => e.type === "htorpedo" || e.type === "vtorpedo")
|
||||||
|
.length ?? 0),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
useGameProps.setState({ mode: 1 })
|
setMode(1)
|
||||||
setTarget((e) => ({ ...e, show: false }))
|
setTarget((t) => ({ ...t, show: false }))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "radar",
|
icon: "radar",
|
||||||
text: "Radar scan",
|
text: "Radar scan",
|
||||||
enabled: mode === 3,
|
enabled: mode() === 3,
|
||||||
amount:
|
amount:
|
||||||
1 -
|
1 -
|
||||||
((selfUser?.moves ?? []).filter((e) => e.type === "radar")
|
(selfUser()
|
||||||
.length ?? 0),
|
?.moves()
|
||||||
|
.filter((e) => e.type === "radar").length ?? 0),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
useGameProps.setState({ mode: 3 })
|
setMode(3)
|
||||||
setTarget((e) => ({ ...e, show: false }))
|
setTarget((t) => ({ ...t, show: false }))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -166,7 +152,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
|
||||||
useGameProps.setState({ mode: 0 })
|
setMode(0)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -175,7 +161,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
|
||||||
useGameProps.setState({ mode: 1 })
|
setMode(1)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -184,7 +170,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
|
||||||
useGameProps.setState({ mode: 2 })
|
setMode(2)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -213,31 +199,31 @@ function EventBar(props: { clear: () => void }) {
|
||||||
{
|
{
|
||||||
icon: faGlasses,
|
icon: faGlasses,
|
||||||
text: "Spectators",
|
text: "Spectators",
|
||||||
disabled: !payload?.game?.allowSpectators,
|
disabled: !allowSpectators(),
|
||||||
callback: gameSetting({
|
callback: setGameSetting({
|
||||||
allowSpectators: !payload?.game?.allowSpectators,
|
allowSpectators: !allowSpectators(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: faSparkles,
|
icon: faSparkles,
|
||||||
text: "Specials",
|
text: "Specials",
|
||||||
disabled: !payload?.game?.allowSpecials,
|
disabled: !allowSpecials(),
|
||||||
callback: gameSetting({
|
callback: setGameSetting({
|
||||||
allowSpecials: !payload?.game?.allowSpecials,
|
allowSpecials: !allowSpecials(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: faComments,
|
icon: faComments,
|
||||||
text: "Chat",
|
text: "Chat",
|
||||||
disabled: !payload?.game?.allowChat,
|
disabled: !allowChat(),
|
||||||
callback: gameSetting({ allowChat: !payload?.game?.allowChat }),
|
callback: setGameSetting({ allowChat: !allowChat() }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: faScribble,
|
icon: faScribble,
|
||||||
text: "Mark/Draw",
|
text: "Mark/Draw",
|
||||||
disabled: !payload?.game?.allowMarkDraw,
|
disabled: !allowMarkDraw(),
|
||||||
callback: gameSetting({
|
callback: setGameSetting({
|
||||||
allowMarkDraw: !payload?.game?.allowMarkDraw,
|
allowMarkDraw: !allowMarkDraw(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -248,7 +234,7 @@ function EventBar(props: { clear: () => void }) {
|
||||||
iconColor: "green",
|
iconColor: "green",
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
socket.emit("gameState", "aborted")
|
socket.emit("gameState", "aborted")
|
||||||
await navigate("/")
|
await navigator("/")
|
||||||
reset()
|
reset()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -257,7 +243,7 @@ function EventBar(props: { clear: () => void }) {
|
||||||
text: "No",
|
text: "No",
|
||||||
iconColor: "red",
|
iconColor: "red",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
useGameProps.setState({ menu: "main" })
|
setMenu("main")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -265,22 +251,22 @@ function EventBar(props: { clear: () => void }) {
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (
|
if (
|
||||||
menu !== "moves" ||
|
menu() !== "moves" ||
|
||||||
payload?.game?.state !== "starting" ||
|
gameState() !== "starting" ||
|
||||||
mode < 0 ||
|
mode() < 0 ||
|
||||||
items().moves[mode].amount
|
items().moves[mode()].amount
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
const index = items().moves.findIndex((e) => e.amount)
|
const index = items().moves.findIndex((e) => e.amount)
|
||||||
useGameProps.setState({ mode: index })
|
setMode(index)
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
useDrawProps.setState({ enable: menu === "draw" })
|
useDrawProps.setState({ enable: menu() === "draw" })
|
||||||
})
|
})
|
||||||
|
|
||||||
// createEffect(() => {
|
// createEffect(() => {
|
||||||
// if (payload?.game?.state !== "running") return
|
// if (gameState() !== "running") return
|
||||||
|
|
||||||
// const toastId = "otherPlayer"
|
// const toastId = "otherPlayer"
|
||||||
// if (isActiveIndex) toast.dismiss(toastId)
|
// if (isActiveIndex) toast.dismiss(toastId)
|
||||||
|
@ -307,59 +293,51 @@ function EventBar(props: { clear: () => void }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="event-bar">
|
<div class="event-bar">
|
||||||
<Show when={menu !== "main"}>
|
<Show when={menu() !== "main"}>
|
||||||
<Item
|
<Item
|
||||||
{...{
|
{...{
|
||||||
icon: faReply,
|
icon: faReply,
|
||||||
text: "Return",
|
text: "Return",
|
||||||
iconColor: "#555",
|
iconColor: "#555",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
useGameProps.setState({ menu: "main" })
|
setMenu("main")
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<For each={items()[menu]}>
|
<For each={items()[menu()]}>
|
||||||
{(e, i) => (
|
{(e, i) => (
|
||||||
<Show when={isActiveIndex && menu !== "main" && i() !== 1}>
|
<Show when={isActiveIndex() && menu() !== "main" && i() !== 1}>
|
||||||
<Item {...e} />
|
<Item {...e} />
|
||||||
</Show>
|
</Show>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
<Show when={menu === "moves"}>
|
<Show when={menu() === "moves"}>
|
||||||
<Item
|
<Item
|
||||||
{...{
|
{...{
|
||||||
icon:
|
icon: selfUser()?.isReady() ? faLock : faCheck,
|
||||||
selfIndex >= 0 && userStates[selfIndex].isReady()
|
text: selfUser()?.isReady() ? "unready" : "Done",
|
||||||
? faLock
|
disabled: gameState() === "starting" ? mode() >= 0 : undefined,
|
||||||
: faCheck,
|
enabled: gameState() === "running" && mode() >= 0 && target().show,
|
||||||
text:
|
|
||||||
selfIndex >= 0 && userStates[selfIndex].isReady()
|
|
||||||
? "unready"
|
|
||||||
: "Done",
|
|
||||||
disabled:
|
|
||||||
payload?.game?.state === "starting" ? mode >= 0 : undefined,
|
|
||||||
enabled:
|
|
||||||
payload?.game?.state === "running" && mode >= 0 && target.show,
|
|
||||||
callback: () => {
|
callback: () => {
|
||||||
if (selfIndex < 0) return
|
const i = selfIndex()
|
||||||
switch (payload?.game?.state) {
|
if (i === -1) return
|
||||||
|
switch (gameState()) {
|
||||||
case "starting":
|
case "starting":
|
||||||
const isReady = !userStates[selfIndex].isReady
|
const isReady = !users[i].isReady()
|
||||||
setIsReady({ isReady, i: selfIndex })
|
setIsReadyFor({ isReady, i })
|
||||||
socket.emit("isReady", isReady)
|
socket.emit("isReady", isReady)
|
||||||
break
|
break
|
||||||
|
|
||||||
case "running":
|
case "running":
|
||||||
const i = (selfUser?.moves ?? [])
|
const moves = selfUser()?.moves()
|
||||||
.map((e) => e.index)
|
const length = moves?.length
|
||||||
.reduce((prev, curr) => (curr > prev ? curr : prev), 0)
|
|
||||||
const props = {
|
const props = {
|
||||||
type: modes[mode].type,
|
type: modes[mode()].type,
|
||||||
x: target.x,
|
x: target().x,
|
||||||
y: target.y,
|
y: target().y,
|
||||||
orientation: target.orientation,
|
orientation: target().orientation,
|
||||||
index: (selfUser?.moves ?? []).length ? i + 1 : 0,
|
index: length ?? 0,
|
||||||
}
|
}
|
||||||
socket.emit("dispatchMove", props)
|
socket.emit("dispatchMove", props)
|
||||||
setTarget((t) => ({ ...t, show: false }))
|
setTarget((t) => ({ ...t, show: false }))
|
||||||
|
|
|
@ -9,8 +9,20 @@ import HitElems from "~/components/Gamefield/HitElems"
|
||||||
import Targets from "~/components/Gamefield/Targets"
|
import Targets from "~/components/Gamefield/Targets"
|
||||||
import { useDraw } from "~/hooks/useDraw"
|
import { useDraw } from "~/hooks/useDraw"
|
||||||
import { useDrawProps } from "~/hooks/useDrawProps"
|
import { useDrawProps } from "~/hooks/useDrawProps"
|
||||||
import { useGameProps } from "~/hooks/useGameProps"
|
import {
|
||||||
import useIndex from "~/hooks/useIndex"
|
full,
|
||||||
|
gameId,
|
||||||
|
gameState,
|
||||||
|
mode,
|
||||||
|
mouseCursor,
|
||||||
|
reset,
|
||||||
|
setMode,
|
||||||
|
setMouseCursor,
|
||||||
|
setTargetPreview,
|
||||||
|
target,
|
||||||
|
users,
|
||||||
|
} from "~/hooks/useGameProps"
|
||||||
|
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"
|
||||||
import { overlapsWithAnyBorder } from "~/lib/utils/helpers"
|
import { overlapsWithAnyBorder } from "~/lib/utils/helpers"
|
||||||
|
@ -20,47 +32,35 @@ import Ships from "./Ships"
|
||||||
export const count = 12
|
export const count = 12
|
||||||
|
|
||||||
function Gamefield() {
|
function Gamefield() {
|
||||||
const { selfUser } = useIndex()
|
const { ships } = useSession()
|
||||||
const navigate = useNavigate()
|
const navigator = useNavigate()
|
||||||
const {
|
|
||||||
userStates,
|
|
||||||
mode,
|
|
||||||
target,
|
|
||||||
mouseCursor,
|
|
||||||
setMouseCursor,
|
|
||||||
payload,
|
|
||||||
setTargetPreview,
|
|
||||||
full,
|
|
||||||
reset,
|
|
||||||
} = useGameProps()
|
|
||||||
const { isConnected } = useSocket()
|
const { isConnected } = useSocket()
|
||||||
|
|
||||||
const usingDraw = useDraw()
|
const usingDraw = useDraw()
|
||||||
const { enable, color, shouldHide } = useDrawProps()
|
const { enable, color, shouldHide } = useDrawProps()
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (
|
if (
|
||||||
payload?.game?.state !== "starting" ||
|
gameState() !== "starting" ||
|
||||||
userStates.reduce((prev, curr) => prev || !curr.isReady, false)
|
!users[0].isReady() ||
|
||||||
|
!users[1].isReady()
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
socket.emit("ships", selfUser?.ships ?? [])
|
socket.emit("ships", ships() ?? [])
|
||||||
socket.emit("gameState", "running")
|
socket.emit("gameState", "running")
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (payload?.game?.id || !isConnected) return
|
if (gameId() || !isConnected()) return
|
||||||
socket.emit("update", full)
|
socket.emit("update", full)
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (mode < 0) return
|
if (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 ||
|
||||||
(payload?.game?.state === "running" &&
|
(gameState() === "running" && overlapsWithAnyBorder(position, mode()))
|
||||||
overlapsWithAnyBorder(position, mode))
|
|
||||||
)
|
)
|
||||||
setTargetPreview((t) => ({ ...t, show: false }))
|
setTargetPreview((t) => ({ ...t, show: false }))
|
||||||
else {
|
else {
|
||||||
|
@ -71,14 +71,14 @@ function Gamefield() {
|
||||||
}))
|
}))
|
||||||
const handleKeyPress = (event: KeyboardEvent) => {
|
const handleKeyPress = (event: KeyboardEvent) => {
|
||||||
if (event.key !== "r") return
|
if (event.key !== "r") return
|
||||||
if (payload?.game?.state === "starting") {
|
if (gameState() === "starting") {
|
||||||
setTargetPreview((t) => ({
|
setTargetPreview((t) => ({
|
||||||
...t,
|
...t,
|
||||||
orientation: t.orientation === "h" ? "v" : "h",
|
orientation: t.orientation === "h" ? "v" : "h",
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
if (payload?.game?.state === "running" && (mode === 1 || mode === 2))
|
if (gameState() === "running" && (mode() === 1 || mode() === 2))
|
||||||
useGameProps.setState({ mode: mode === 1 ? 2 : 1 })
|
setMode(mode() === 1 ? 2 : 1)
|
||||||
}
|
}
|
||||||
document.addEventListener("keydown", handleKeyPress)
|
document.addEventListener("keydown", handleKeyPress)
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -88,15 +88,15 @@ function Gamefield() {
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (payload?.game?.state !== "aborted") return
|
if (gameState() !== "aborted") return
|
||||||
// toast.info("Enemy gave up!")
|
// toast.info("Enemy gave up!")
|
||||||
navigate("/")
|
navigator("/")
|
||||||
reset()
|
reset()
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (payload?.game?.id) return
|
if (gameId()) return
|
||||||
const timeout = setTimeout(() => navigate("/"), 5000)
|
const timeout = setTimeout(() => navigator("/"), 5000)
|
||||||
return () => clearTimeout(timeout)
|
return () => clearTimeout(timeout)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import { faBurst, faXmark } from "@fortawesome/pro-solid-svg-icons"
|
import { faBurst, faXmark } from "@fortawesome/pro-solid-svg-icons"
|
||||||
import { For } from "solid-js"
|
import { For } from "solid-js"
|
||||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
||||||
import useIndex from "~/hooks/useIndex"
|
import { useSession } from "~/hooks/useSession"
|
||||||
import { Hit } from "../../interfaces/frontend"
|
import { Hit } from "../../interfaces/frontend"
|
||||||
|
|
||||||
function HitElems(props: { hits?: Hit[]; colorOverride?: string }) {
|
function HitElems(props: { hits?: Hit[]; colorOverride?: string }) {
|
||||||
const { activeUser } = useIndex()
|
const { activeUser } = useSession()
|
||||||
const hits = () => props?.hits
|
const hits = () => props?.hits
|
||||||
const colorOverride = () => props?.colorOverride
|
const colorOverride = () => props?.colorOverride
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<For each={hits() ?? activeUser?.hits ?? []}>
|
<For each={hits() ?? activeUser()?.hits()}>
|
||||||
{(props) => (
|
{(props) => (
|
||||||
<div class="hit-svg" style={{ "--x": props.x, "--y": props.y }}>
|
<div class="hit-svg" style={{ "--x": props.x, "--y": props.y }}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { createEffect, createSignal } from "solid-js"
|
import { Show, createEffect, createSignal } from "solid-js"
|
||||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
||||||
// import { useDrawProps } from "~/hooks/useDrawProps"
|
// import { useDrawProps } from "~/hooks/useDrawProps"
|
||||||
// import { HexColorPicker } from "react-colorful"
|
// import { HexColorPicker } from "react-colorful"
|
||||||
|
@ -33,14 +33,14 @@ function Item(props: ItemProps) {
|
||||||
isColor() ? setActive(true) : props.callback && props.callback()
|
isColor() ? setActive(true) : props.callback && props.callback()
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isColor() ? (
|
<Show when={isColor()}>
|
||||||
<div
|
<div
|
||||||
ref={cpRef!}
|
ref={cpRef!}
|
||||||
class={classNames("react-colorful-wrapper", { active: active })}
|
class={classNames("react-colorful-wrapper", { active: active })}
|
||||||
>
|
>
|
||||||
{/* <HexColorPicker color={color} onChange={setColor} /> */}
|
{/* <HexColorPicker color={color} onChange={setColor} /> */}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
</Show>
|
||||||
<div
|
<div
|
||||||
class={classNames("container", {
|
class={classNames("container", {
|
||||||
amount: typeof props.amount !== "undefined",
|
amount: typeof props.amount !== "undefined",
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
import { For, Show } from "solid-js"
|
import { For, Show } from "solid-js"
|
||||||
import { useGameProps } from "~/hooks/useGameProps"
|
import { gameState } from "~/hooks/useGameProps"
|
||||||
import useIndex from "~/hooks/useIndex"
|
import { useSession } from "~/hooks/useSession"
|
||||||
import Ship from "./Ship"
|
import Ship from "./Ship"
|
||||||
|
|
||||||
function Ships() {
|
function Ships() {
|
||||||
const { payload } = useGameProps()
|
const { isActiveIndex, selfUser } = useSession()
|
||||||
const { isActiveIndex, selfUser } = useIndex()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={payload?.game?.state === "running" && isActiveIndex}>
|
<Show when={gameState() === "running" && isActiveIndex}>
|
||||||
<For each={selfUser?.ships}>{(props) => <Ship {...props} />}</For>
|
<For each={selfUser()?.ships()}>{(props) => <Ship {...props} />}</For>
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { For, Match, Switch } from "solid-js"
|
import { For, Match, Switch } from "solid-js"
|
||||||
import { useGameProps } from "~/hooks/useGameProps"
|
import { gameState, mode, target, targetPreview } from "~/hooks/useGameProps"
|
||||||
import useIndex from "~/hooks/useIndex"
|
import { useSession } from "~/hooks/useSession"
|
||||||
import useShips from "~/hooks/useShips"
|
|
||||||
import {
|
import {
|
||||||
composeTargetTiles,
|
composeTargetTiles,
|
||||||
intersectingShip,
|
intersectingShip,
|
||||||
|
@ -12,29 +11,29 @@ import HitElems from "./HitElems"
|
||||||
import Ship from "./Ship"
|
import Ship from "./Ship"
|
||||||
|
|
||||||
function Targets() {
|
function Targets() {
|
||||||
const { activeUser } = useIndex()
|
const { activeUser, ships } = useSession()
|
||||||
const { payload, target, targetPreview, mode } = useGameProps()
|
|
||||||
const { ships } = useShips()
|
|
||||||
|
|
||||||
const ship = shipProps(ships(), mode, targetPreview)
|
const ship = shipProps(ships(), mode(), targetPreview())
|
||||||
const { fields, borders, score } = intersectingShip(ships(), ship)
|
const { fields, borders, score } = intersectingShip(ships(), ship)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={payload?.game?.state === "running"}>
|
<Match when={gameState() === "running"}>
|
||||||
<For each={composeTargetTiles(target, mode, activeUser?.hits ?? [])}>
|
<For each={composeTargetTiles(target(), mode(), activeUser().hits())}>
|
||||||
{(props) => <GamefieldPointer {...props} />}
|
{(props) => <GamefieldPointer {...props} />}
|
||||||
</For>
|
</For>
|
||||||
<For
|
<For
|
||||||
each={composeTargetTiles(targetPreview, mode, activeUser?.hits ?? [])}
|
each={composeTargetTiles(
|
||||||
|
targetPreview(),
|
||||||
|
mode(),
|
||||||
|
activeUser().hits(),
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{(props) => <GamefieldPointer {...props} preview />}
|
{(props) => <GamefieldPointer {...props} preview />}
|
||||||
</For>
|
</For>
|
||||||
</Match>
|
</Match>
|
||||||
<Match
|
<Match
|
||||||
when={
|
when={gameState() === "starting" && mode() >= 0 && targetPreview().show}
|
||||||
payload?.game?.state === "starting" && mode >= 0 && targetPreview.show
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Ship
|
<Ship
|
||||||
{...ship}
|
{...ship}
|
||||||
|
|
|
@ -2,14 +2,21 @@ import {
|
||||||
faRightFromBracket,
|
faRightFromBracket,
|
||||||
faSpinnerThird,
|
faSpinnerThird,
|
||||||
} from "@fortawesome/pro-solid-svg-icons"
|
} from "@fortawesome/pro-solid-svg-icons"
|
||||||
import { JSX, createEffect, createSignal } from "solid-js"
|
import { JSX, Show, createEffect, createSignal } from "solid-js"
|
||||||
import { useNavigate } from "solid-start"
|
import { useNavigate } from "solid-start"
|
||||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
||||||
import { useGameProps } from "~/hooks/useGameProps"
|
import {
|
||||||
|
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"
|
||||||
|
|
||||||
import Button from "./Button"
|
import Button from "./Button"
|
||||||
import Icon from "./Icon"
|
import Icon from "./Icon"
|
||||||
import Player from "./Player"
|
import Player from "./Player"
|
||||||
|
@ -34,15 +41,11 @@ function WithDots(props: { children: JSX.Element }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function LobbyFrame(props: { openSettings: () => void }) {
|
function LobbyFrame(props: { openSettings: () => void }) {
|
||||||
const { payload, userStates, full, leave, reset } = useGameProps()
|
|
||||||
const { isConnected } = useSocket()
|
const { isConnected } = useSocket()
|
||||||
const navigate = 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 = () =>
|
|
||||||
payload?.users.length === 2 &&
|
|
||||||
!userStates.filter((user) => !user.isReady).length
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!launching() || launchTime() > 0) return
|
if (!launching() || launchTime() > 0) return
|
||||||
|
@ -61,17 +64,13 @@ function LobbyFrame(props: { openSettings: () => void }) {
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (payload?.game?.id || !isConnected) return
|
if (gameId() || !isConnected()) return
|
||||||
socket.emit("update", full)
|
socket.emit("update", full)
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (
|
if (gameState() === "unknown" || gameState() === "lobby") return
|
||||||
typeof payload?.game?.state !== "string" ||
|
navigator("/gamefield")
|
||||||
payload?.game?.state === "lobby"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
navigate("/gamefield")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -79,53 +78,50 @@ function LobbyFrame(props: { openSettings: () => void }) {
|
||||||
<div class="flex items-center justify-between border-b-2 border-slate-900">
|
<div class="flex items-center justify-between border-b-2 border-slate-900">
|
||||||
<Icon src="speech_bubble.png">Chat</Icon>
|
<Icon src="speech_bubble.png">Chat</Icon>
|
||||||
<h1 class="font-farro text-5xl font-medium">
|
<h1 class="font-farro text-5xl font-medium">
|
||||||
{launching() ? (
|
<Show
|
||||||
|
when={!launching()}
|
||||||
|
fallback={
|
||||||
<WithDots>
|
<WithDots>
|
||||||
{launchTime() < 0
|
{launchTime() < 0
|
||||||
? "Game starts"
|
? "Game starts"
|
||||||
: "Game is starting in " + launchTime()}
|
: "Game is starting in " + launchTime()}
|
||||||
</WithDots>
|
</WithDots>
|
||||||
) : (
|
}
|
||||||
<>
|
>
|
||||||
{"Game-PIN: "}
|
{"Game-PIN: "}
|
||||||
{isConnected() ? (
|
<Show
|
||||||
<span class="underline">{payload?.gamePin ?? "----"}</span>
|
when={isConnected()}
|
||||||
) : (
|
fallback={<FontAwesomeIcon icon={faSpinnerThird} spin />}
|
||||||
<FontAwesomeIcon icon={faSpinnerThird} spin />
|
>
|
||||||
)}
|
<span class="underline">{gamePin() ?? "----"}</span>
|
||||||
</>
|
</Show>
|
||||||
)}
|
</Show>
|
||||||
</h1>
|
</h1>
|
||||||
<Icon src="gear.png" onClick={props.openSettings}>
|
<Icon src="gear.png" onClick={props.openSettings}>
|
||||||
Settings
|
Settings
|
||||||
</Icon>
|
</Icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-around">
|
<div class="flex items-center justify-around">
|
||||||
{isConnected() ? (
|
<Show
|
||||||
|
when={isConnected()}
|
||||||
|
fallback={
|
||||||
|
<p class="font-farro m-48 text-center text-6xl font-medium">
|
||||||
|
Warte auf Verbindung
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
>
|
||||||
<>
|
<>
|
||||||
<Player
|
<Player src="player_blue.png" i={0} userId={session()?.user?.id} />
|
||||||
src="player_blue.png"
|
|
||||||
i={0}
|
|
||||||
userId={session.latest?.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>
|
||||||
{payload?.users[1] ? (
|
{users[1].id() ? (
|
||||||
<Player
|
<Player src="player_red.png" i={1} userId={session()?.user?.id} />
|
||||||
src="player_red.png"
|
|
||||||
i={1}
|
|
||||||
userId={session.latest?.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">
|
||||||
<WithDots>Warte auf Spieler 2</WithDots>
|
<WithDots>Warte auf Spieler 2</WithDots>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
</Show>
|
||||||
<p class="font-farro m-48 text-center text-6xl font-medium">
|
|
||||||
Warte auf Verbindung
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-around border-t-2 border-slate-900 p-4">
|
<div class="flex items-center justify-around border-t-2 border-slate-900 p-4">
|
||||||
<Button
|
<Button
|
||||||
|
@ -134,7 +130,7 @@ function LobbyFrame(props: { openSettings: () => void }) {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
leave(async () => {
|
leave(async () => {
|
||||||
reset()
|
reset()
|
||||||
await navigate("/")
|
navigator("/")
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -8,9 +8,9 @@ import {
|
||||||
} from "@fortawesome/pro-solid-svg-icons"
|
} from "@fortawesome/pro-solid-svg-icons"
|
||||||
import { faCaretDown } from "@fortawesome/sharp-solid-svg-icons"
|
import { faCaretDown } from "@fortawesome/sharp-solid-svg-icons"
|
||||||
import classNames from "classnames"
|
import classNames from "classnames"
|
||||||
import { createEffect, createSignal } from "solid-js"
|
import { Show, createEffect, createSignal } from "solid-js"
|
||||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
||||||
import { useGameProps } from "~/hooks/useGameProps"
|
import { setIsReadyFor, users } from "~/hooks/useGameProps"
|
||||||
import { socket } from "~/lib/socket"
|
import { socket } from "~/lib/socket"
|
||||||
import Button from "./Button"
|
import Button from "./Button"
|
||||||
|
|
||||||
|
@ -42,13 +42,11 @@ function HourGlass() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Player(props: { src: string; i: number; userId?: string }) {
|
function Player(props: { src: string; i: 0 | 1; userId?: string }) {
|
||||||
const { payload, userStates, setIsReady } = useGameProps()
|
const player = () => users[props.i]
|
||||||
const player = () => payload?.users[props.i]
|
const isReady = () => users[props.i].isReady()
|
||||||
const isReady = () => userStates[props.i].isReady
|
const isConnected = () => users[props.i].isConnected()
|
||||||
const isConnected = () => userStates[props.i].isConnected
|
const primary = () => props.userId && props.userId === users[props.i].id()
|
||||||
const primary = () =>
|
|
||||||
props.userId && props.userId === payload?.users[props.i]?.id
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex w-96 flex-col items-center gap-4 p-4">
|
<div class="flex w-96 flex-col items-center gap-4 p-4">
|
||||||
|
@ -58,7 +56,7 @@ function Player(props: { src: string; i: number; userId?: string }) {
|
||||||
primary() ? "font-semibold" : "font-normal",
|
primary() ? "font-semibold" : "font-normal",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{player?.name ?? "Spieler " + (player()?.index === 2 ? "2" : "1")}
|
{player().name() ?? "Spieler " + (props.i === 1 ? "2" : "1")}
|
||||||
</p>
|
</p>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<img
|
<img
|
||||||
|
@ -66,14 +64,14 @@ function Player(props: { src: string; i: number; userId?: string }) {
|
||||||
src={"/assets/" + props.src}
|
src={"/assets/" + props.src}
|
||||||
alt={props.src}
|
alt={props.src}
|
||||||
/>
|
/>
|
||||||
{primary() ? (
|
<Show when={primary()}>
|
||||||
<button class="absolute right-4 top-4 h-14 w-14 rounded-lg border-2 border-dashed border-warn bg-gray-800 bg-opacity-90">
|
<button class="absolute right-4 top-4 h-14 w-14 rounded-lg border-2 border-dashed border-warn bg-gray-800 bg-opacity-90">
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
class="h-full w-full text-warn"
|
class="h-full w-full text-warn"
|
||||||
icon={faCaretDown}
|
icon={faCaretDown}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
type={isConnected() ? (isReady() ? "green" : "orange") : "gray"}
|
type={isConnected() ? (isReady() ? "green" : "orange") : "gray"}
|
||||||
|
@ -81,11 +79,11 @@ function Player(props: { src: string; i: number; userId?: string }) {
|
||||||
isLatched={!!isReady()}
|
isLatched={!!isReady()}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!player()) return
|
if (!player()) return
|
||||||
setIsReady({
|
socket.emit("isReady", !isReady())
|
||||||
|
setIsReadyFor({
|
||||||
i: props.i,
|
i: props.i,
|
||||||
isReady: !isReady(),
|
isReady: !isReady(),
|
||||||
})
|
})
|
||||||
socket.emit("isReady", !isReady())
|
|
||||||
}}
|
}}
|
||||||
disabled={!primary()}
|
disabled={!primary()}
|
||||||
>
|
>
|
||||||
|
|
|
@ -5,13 +5,25 @@ import {
|
||||||
import classNames from "classnames"
|
import classNames from "classnames"
|
||||||
import { JSX } from "solid-js"
|
import { JSX } from "solid-js"
|
||||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
||||||
import { setGameSetting } from "~/components/Gamefield/EventBar"
|
import {
|
||||||
import { useGameProps } from "~/hooks/useGameProps"
|
allowChat,
|
||||||
|
allowMarkDraw,
|
||||||
|
allowSpecials,
|
||||||
|
allowSpectators,
|
||||||
|
setGameSetting,
|
||||||
|
} from "~/hooks/useGameProps"
|
||||||
import { GameSettingKeys } from "../../../interfaces/frontend"
|
import { GameSettingKeys } from "../../../interfaces/frontend"
|
||||||
|
|
||||||
function Setting(props: { children: JSX.Element; key: GameSettingKeys }) {
|
function Setting(props: { children: JSX.Element; key: GameSettingKeys }) {
|
||||||
const { payload, setSetting, full } = useGameProps()
|
const state = () => {
|
||||||
const state = () => payload?.game?.[props.key]
|
const gameProps = {
|
||||||
|
allowChat,
|
||||||
|
allowMarkDraw,
|
||||||
|
allowSpecials,
|
||||||
|
allowSpectators,
|
||||||
|
}
|
||||||
|
return gameProps[props.key]()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label class="flex items-center justify-between" for={props.key}>
|
<label class="flex items-center justify-between" for={props.key}>
|
||||||
|
@ -23,7 +35,7 @@ function Setting(props: { children: JSX.Element; key: GameSettingKeys }) {
|
||||||
"text-md mx-auto rounded-full px-4 drop-shadow-md transition-all",
|
"text-md mx-auto rounded-full px-4 drop-shadow-md transition-all",
|
||||||
state() ? "text-blue-500" : "text-gray-800",
|
state() ? "text-blue-500" : "text-gray-800",
|
||||||
{
|
{
|
||||||
"bg-gray-300 ": state,
|
"bg-gray-300 ": state(),
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
size="3x"
|
size="3x"
|
||||||
|
@ -35,13 +47,9 @@ function Setting(props: { children: JSX.Element; key: GameSettingKeys }) {
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id={props.key}
|
id={props.key}
|
||||||
onChange={() =>
|
onChange={() =>
|
||||||
setGameSetting(
|
setGameSetting({
|
||||||
{
|
[props.key]: !state(),
|
||||||
[props.key]: !state,
|
})
|
||||||
},
|
|
||||||
setSetting,
|
|
||||||
full,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
hidden={true}
|
hidden={true}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -2,17 +2,15 @@ import { faRotateLeft } from "@fortawesome/pro-regular-svg-icons"
|
||||||
import { faXmark } from "@fortawesome/pro-solid-svg-icons"
|
import { faXmark } from "@fortawesome/pro-solid-svg-icons"
|
||||||
import {} from "solid-js"
|
import {} from "solid-js"
|
||||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
||||||
import { useGameProps } from "~/hooks/useGameProps"
|
import { full, setSetting } from "~/hooks/useGameProps"
|
||||||
import { socket } from "~/lib/socket"
|
import { socket } from "~/lib/socket"
|
||||||
import { GameSettings } from "../../../interfaces/frontend"
|
import { GameSettings } from "../../../interfaces/frontend"
|
||||||
import Setting from "./Setting"
|
import Setting from "./Setting"
|
||||||
|
|
||||||
function Settings(props: { closeSettings: () => void }) {
|
function Settings(props: { closeSettings: () => void }) {
|
||||||
const { setSetting, full } = useGameProps()
|
const gameSetting = (newSettings: GameSettings) => {
|
||||||
|
const hash = setSetting(newSettings)
|
||||||
const gameSetting = (payload: GameSettings) => {
|
socket.emit("gameSetting", newSettings, (newHash) => {
|
||||||
const hash = setSetting(payload)
|
|
||||||
socket.emit("gameSetting", payload, (newHash) => {
|
|
||||||
if (newHash === hash) return
|
if (newHash === hash) return
|
||||||
console.log("hash", hash, newHash)
|
console.log("hash", hash, newHash)
|
||||||
socket.emit("update", full)
|
socket.emit("update", full)
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import type { AdapterAccount } from "@auth/core/adapters"
|
import type { AdapterAccount } from "@auth/core/adapters"
|
||||||
import { createId } from "@paralleldrive/cuid2"
|
|
||||||
import { relations } from "drizzle-orm"
|
import { relations } from "drizzle-orm"
|
||||||
import {
|
import {
|
||||||
AnyPgColumn,
|
|
||||||
boolean,
|
boolean,
|
||||||
integer,
|
integer,
|
||||||
pgTable,
|
pgTable,
|
||||||
|
@ -15,7 +13,7 @@ import { gameState, moveType, orientation } from "./Types"
|
||||||
|
|
||||||
export const users = pgTable("user", {
|
export const users = pgTable("user", {
|
||||||
id: text("id").notNull().primaryKey(),
|
id: text("id").notNull().primaryKey(),
|
||||||
name: text("name"),
|
name: text("name").notNull(),
|
||||||
email: text("email").notNull().unique(),
|
email: text("email").notNull().unique(),
|
||||||
emailVerified: timestamp("emailVerified", { mode: "date" }),
|
emailVerified: timestamp("emailVerified", { mode: "date" }),
|
||||||
image: text("image"),
|
image: text("image"),
|
||||||
|
@ -69,7 +67,7 @@ export const verificationTokens = pgTable(
|
||||||
)
|
)
|
||||||
|
|
||||||
export const games = pgTable("game", {
|
export const games = pgTable("game", {
|
||||||
id: text("id").notNull().primaryKey().default(createId()),
|
id: text("id").notNull().primaryKey(),
|
||||||
createdAt: timestamp("created_at").defaultNow(),
|
createdAt: timestamp("created_at").defaultNow(),
|
||||||
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||||
state: gameState("state").notNull().default("lobby"),
|
state: gameState("state").notNull().default("lobby"),
|
||||||
|
@ -80,17 +78,17 @@ export const games = pgTable("game", {
|
||||||
})
|
})
|
||||||
|
|
||||||
export const gamepins = pgTable("gamepin", {
|
export const gamepins = pgTable("gamepin", {
|
||||||
id: text("id").notNull().primaryKey().default(createId()),
|
id: text("id").notNull().primaryKey(),
|
||||||
createdAt: timestamp("created_at").defaultNow(),
|
createdAt: timestamp("created_at").defaultNow(),
|
||||||
pin: text("pin").notNull().unique(),
|
pin: text("pin").notNull().unique(),
|
||||||
gameId: text("game_id")
|
gameId: text("game_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
.unique()
|
.unique()
|
||||||
.references((): AnyPgColumn => games.id),
|
.references(() => games.id, { onDelete: "cascade" }),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const ships = pgTable("ship", {
|
export const ships = pgTable("ship", {
|
||||||
id: text("id").notNull().primaryKey().default(createId()),
|
id: text("id").notNull().primaryKey(),
|
||||||
size: integer("size").notNull(),
|
size: integer("size").notNull(),
|
||||||
variant: integer("variant").notNull(),
|
variant: integer("variant").notNull(),
|
||||||
x: integer("x").notNull(),
|
x: integer("x").notNull(),
|
||||||
|
@ -102,7 +100,7 @@ export const ships = pgTable("ship", {
|
||||||
})
|
})
|
||||||
|
|
||||||
export const hits = pgTable("hit", {
|
export const hits = pgTable("hit", {
|
||||||
id: text("id").notNull().primaryKey().default(createId()),
|
id: text("id").notNull().primaryKey(),
|
||||||
x: integer("x").notNull(),
|
x: integer("x").notNull(),
|
||||||
y: integer("y").notNull(),
|
y: integer("y").notNull(),
|
||||||
hit: boolean("hit").notNull(),
|
hit: boolean("hit").notNull(),
|
||||||
|
@ -112,7 +110,7 @@ export const hits = pgTable("hit", {
|
||||||
})
|
})
|
||||||
|
|
||||||
export const moves = pgTable("move", {
|
export const moves = pgTable("move", {
|
||||||
id: text("id").notNull().primaryKey().default(createId()),
|
id: text("id").notNull().primaryKey(),
|
||||||
createdAt: timestamp("created_at").defaultNow(),
|
createdAt: timestamp("created_at").defaultNow(),
|
||||||
index: integer("index").notNull(),
|
index: integer("index").notNull(),
|
||||||
type: moveType("type").notNull(),
|
type: moveType("type").notNull(),
|
||||||
|
@ -125,7 +123,7 @@ export const moves = pgTable("move", {
|
||||||
})
|
})
|
||||||
|
|
||||||
export const chats = pgTable("chat", {
|
export const chats = pgTable("chat", {
|
||||||
id: text("id").notNull().primaryKey().default(createId()),
|
id: text("id").notNull().primaryKey(),
|
||||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||||
message: text("message"),
|
message: text("message"),
|
||||||
event: text("event"),
|
event: text("event"),
|
||||||
|
@ -137,7 +135,7 @@ export const chats = pgTable("chat", {
|
||||||
export const user_games = pgTable(
|
export const user_games = pgTable(
|
||||||
"user_game",
|
"user_game",
|
||||||
{
|
{
|
||||||
id: text("id").notNull().primaryKey().default(createId()),
|
id: text("id").notNull().primaryKey(),
|
||||||
createdAt: timestamp("created_at").defaultNow(),
|
createdAt: timestamp("created_at").defaultNow(),
|
||||||
gameId: text("game_id")
|
gameId: text("game_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { pgEnum } from "drizzle-orm/pg-core"
|
import { pgEnum } from "drizzle-orm/pg-core"
|
||||||
|
|
||||||
export const gameState = pgEnum("game_state", [
|
export const gameState = pgEnum("game_state", [
|
||||||
|
"unknown",
|
||||||
"lobby",
|
"lobby",
|
||||||
"starting",
|
"starting",
|
||||||
"running",
|
"running",
|
||||||
|
|
|
@ -1,110 +1,82 @@
|
||||||
import { getPayloadwithChecksum } from "~/lib/getPayloadwithChecksum"
|
/* eslint-disable solid/reactivity */
|
||||||
import { socket } from "~/lib/socket"
|
import { socket } from "~/lib/socket"
|
||||||
|
import { GamePropsSchema, GameState, MoveType } from "~/lib/zodSchemas"
|
||||||
|
// import { toast } from "react-toastify"
|
||||||
|
import { createSignal } from "solid-js"
|
||||||
|
import { getPayloadFromProps } from "~/lib/getPayloadFromProps"
|
||||||
|
import { getPayloadwithChecksum } from "~/lib/getPayloadwithChecksum"
|
||||||
import {
|
import {
|
||||||
|
initialUser,
|
||||||
initlialMouseCursor,
|
initlialMouseCursor,
|
||||||
initlialTarget,
|
initlialTarget,
|
||||||
initlialTargetPreview,
|
initlialTargetPreview,
|
||||||
intersectingShip,
|
intersectingShip,
|
||||||
targetList,
|
targetList,
|
||||||
} from "~/lib/utils/helpers"
|
} from "~/lib/utils/helpers"
|
||||||
import {
|
|
||||||
GamePropsSchema,
|
|
||||||
GameState,
|
|
||||||
MoveType,
|
|
||||||
PlayerSchema,
|
|
||||||
optionalGamePropsSchema,
|
|
||||||
} from "~/lib/zodSchemas"
|
|
||||||
// import { toast } from "react-toastify"
|
|
||||||
import create from "solid-zustand"
|
|
||||||
|
|
||||||
import { Accessor, Setter, createSignal } from "solid-js"
|
|
||||||
import {
|
import {
|
||||||
EventBarModes,
|
EventBarModes,
|
||||||
GameSettings,
|
GameSettings,
|
||||||
MouseCursor,
|
MouseCursor,
|
||||||
MoveDispatchProps,
|
MoveDispatchProps,
|
||||||
|
NewUsers,
|
||||||
ShipProps,
|
ShipProps,
|
||||||
Target,
|
Target,
|
||||||
TargetPreview,
|
TargetPreview,
|
||||||
} from "../interfaces/frontend"
|
} from "../interfaces/frontend"
|
||||||
|
|
||||||
const initialState: optionalGamePropsSchema & {
|
export const [hash, setHash] = createSignal<string | null>(null)
|
||||||
userStates: {
|
export const [activeIndex, setActiveIndex] = createSignal<0 | 1>(0)
|
||||||
isReady: Accessor<boolean>
|
export const [gamePin, setGamePin] = createSignal<string | null>(null)
|
||||||
setIsReady: Setter<boolean>
|
export const [gameId, setGameId] = createSignal<string>("")
|
||||||
isConnected: Accessor<boolean>
|
export const [gameState, setGameState] = createSignal<GameState>("unknown")
|
||||||
setIsConnected: Setter<boolean>
|
export const [allowChat, setAllowChat] = createSignal(false)
|
||||||
}[]
|
export const [allowMarkDraw, setAllowMarkDraw] = createSignal(false)
|
||||||
menu: keyof EventBarModes
|
export const [allowSpecials, setAllowSpecials] = createSignal(false)
|
||||||
mode: number
|
export const [allowSpectators, setallowSpectators] = createSignal(false)
|
||||||
target: Target
|
export const [menu, setMenu] = createSignal<keyof EventBarModes>("moves")
|
||||||
targetPreview: TargetPreview
|
export const [mode, setMode] = createSignal(0)
|
||||||
mouseCursor: MouseCursor
|
export const [target, setTarget] = createSignal<Target>(initlialTarget)
|
||||||
} = {
|
export const [targetPreview, setTargetPreview] = createSignal<TargetPreview>(
|
||||||
menu: "moves",
|
initlialTargetPreview,
|
||||||
mode: 0,
|
)
|
||||||
payload: null,
|
export const [mouseCursor, setMouseCursor] =
|
||||||
hash: null,
|
createSignal<MouseCursor>(initlialMouseCursor)
|
||||||
target: initlialTarget,
|
export const users = {
|
||||||
targetPreview: initlialTargetPreview,
|
0: initialUser(),
|
||||||
mouseCursor: initlialMouseCursor,
|
1: initialUser(),
|
||||||
userStates: Array.from(Array(2), () => {
|
forEach(cb: (user: ReturnType<typeof initialUser>, i: 0 | 1) => void) {
|
||||||
const [isReady, setIsReady] = createSignal(false)
|
cb(this[0], 0)
|
||||||
const [isConnected, setIsConnected] = createSignal(false)
|
cb(this[1], 1)
|
||||||
return { isReady, setIsReady, isConnected, setIsConnected }
|
},
|
||||||
}),
|
map<T>(cb: (user: ReturnType<typeof initialUser>, i: 0 | 1) => T) {
|
||||||
|
return { 0: cb(this[0], 0), 1: cb(this[1], 1) }
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State = typeof initialState
|
// export function setActiveIndex(i: number, selfIndex: number) {
|
||||||
|
// if (!payload()) return
|
||||||
export type Action = {
|
// payload().activeIndex = i
|
||||||
DispatchMove: (props: MoveDispatchProps, i: number) => void
|
// if (i === selfIndex) {
|
||||||
setTarget: (target: Target | ((i: Target) => Target)) => void
|
// setMenu("moves")
|
||||||
setTargetPreview: (
|
// setMode(0)
|
||||||
targetPreview: TargetPreview | ((i: TargetPreview) => TargetPreview),
|
// } else {
|
||||||
) => void
|
// setMenu("main")
|
||||||
setMouseCursor: (
|
// setMode(-1)
|
||||||
mouseCursor: MouseCursor | ((i: MouseCursor) => MouseCursor),
|
// }
|
||||||
) => void
|
// }
|
||||||
setPlayer: (payload: { users: PlayerSchema[] }) => string | null
|
export function DispatchMove(move: MoveDispatchProps, index: number) {
|
||||||
setSetting: (settings: GameSettings) => string | null
|
|
||||||
full: (newProps: GamePropsSchema) => void
|
|
||||||
leave: (cb: () => void) => void
|
|
||||||
setIsReady: (payload: { i: number; isReady: boolean }) => void
|
|
||||||
gameState: (newState: GameState) => void
|
|
||||||
setShips: (ships: ShipProps[], index: number) => void
|
|
||||||
removeShip: (props: ShipProps, index: number) => void
|
|
||||||
setIsConnected: (payload: { i: number; isConnected: boolean }) => void
|
|
||||||
reset: () => void
|
|
||||||
setActiveIndex: (i: number, selfIndex: number) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useGameProps = create<State & Action>()((set) => ({
|
|
||||||
...initialState,
|
|
||||||
setActiveIndex: (i, selfIndex) =>
|
|
||||||
set((state: State) => {
|
|
||||||
if (!state.payload) return state
|
|
||||||
state.payload.activeIndex = i
|
|
||||||
if (i === selfIndex) {
|
|
||||||
state.menu = "moves"
|
|
||||||
state.mode = 0
|
|
||||||
} else {
|
|
||||||
state.menu = "main"
|
|
||||||
state.mode = -1
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
}),
|
|
||||||
DispatchMove: (move, i) =>
|
|
||||||
set((state: State) => {
|
|
||||||
if (!state.payload) return state
|
|
||||||
const list = targetList(move, move.type)
|
const list = targetList(move, move.type)
|
||||||
state.payload.users.map((e) => {
|
users.forEach((user, i) => {
|
||||||
if (!e) return e
|
if (!user) return
|
||||||
if (i === e.index) e.moves.push(move)
|
|
||||||
else if (move.type !== MoveType.Enum.radar)
|
if (index === i) {
|
||||||
e.hits.push(
|
user.setMoves((e) => [...e, move])
|
||||||
|
} else {
|
||||||
|
if (move.type === MoveType.Enum.radar) return
|
||||||
|
user.setHits((e) => [
|
||||||
|
...e,
|
||||||
...list.map(({ x, y }) => ({
|
...list.map(({ x, y }) => ({
|
||||||
hit: !!intersectingShip(e.ships, {
|
hit: !!intersectingShip(user.ships(), {
|
||||||
...move,
|
...move,
|
||||||
size: 1,
|
size: 1,
|
||||||
variant: 0,
|
variant: 0,
|
||||||
|
@ -112,150 +84,179 @@ export const useGameProps = create<State & Action>()((set) => ({
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
})),
|
})),
|
||||||
)
|
])
|
||||||
|
}
|
||||||
return e
|
|
||||||
})
|
})
|
||||||
return state
|
}
|
||||||
}),
|
export function setShips(ships: ShipProps[], index: number) {
|
||||||
setTarget: (dispatch) =>
|
users.forEach(({ setShips }, i) => {
|
||||||
set((state: State) => {
|
if (index !== i) return
|
||||||
if (typeof dispatch === "function") state.target = dispatch(state.target)
|
setShips(ships)
|
||||||
else state.target = dispatch
|
|
||||||
return state
|
|
||||||
}),
|
|
||||||
setTargetPreview: (dispatch) =>
|
|
||||||
set((state: State) => {
|
|
||||||
if (typeof dispatch === "function")
|
|
||||||
state.targetPreview = dispatch(state.targetPreview)
|
|
||||||
else state.targetPreview = dispatch
|
|
||||||
return state
|
|
||||||
}),
|
|
||||||
setMouseCursor: (dispatch) =>
|
|
||||||
set((state: State) => {
|
|
||||||
if (typeof dispatch === "function")
|
|
||||||
state.mouseCursor = dispatch(state.mouseCursor)
|
|
||||||
else state.mouseCursor = dispatch
|
|
||||||
return state
|
|
||||||
}),
|
|
||||||
setShips: (ships, index) =>
|
|
||||||
set((state: State) => {
|
|
||||||
if (!state.payload) return state
|
|
||||||
state.payload.users = state.payload.users.map((e) => {
|
|
||||||
if (!e || e.index !== index) return e
|
|
||||||
e.ships = ships
|
|
||||||
return e
|
|
||||||
})
|
})
|
||||||
return state
|
}
|
||||||
}),
|
export function removeShip({ size, variant, x, y }: ShipProps, index: number) {
|
||||||
removeShip: ({ size, variant, x, y }, index) =>
|
users.forEach((user, i) => {
|
||||||
set((state: State) => {
|
if (index !== i) return
|
||||||
state.payload?.users.map((e) => {
|
const indexToRemove = user
|
||||||
if (!e || e.index !== index) return
|
.ships()
|
||||||
const indexToRemove = e.ships.findIndex(
|
.findIndex(
|
||||||
(ship) =>
|
(ship) =>
|
||||||
ship.size === size &&
|
ship.size === size &&
|
||||||
ship.variant === variant &&
|
ship.variant === variant &&
|
||||||
ship.x === x &&
|
ship.x === x &&
|
||||||
ship.y === y,
|
ship.y === y,
|
||||||
)
|
)
|
||||||
e.ships.splice(indexToRemove, 1)
|
user.setShips((ships) => ships.filter((_, i) => i !== indexToRemove))
|
||||||
return e
|
|
||||||
})
|
})
|
||||||
return state
|
}
|
||||||
}),
|
|
||||||
setPlayer: (payload) => {
|
|
||||||
let hash: string | null = null
|
|
||||||
set((state: State) => {
|
|
||||||
if (!state.payload) return state
|
|
||||||
state.payload.users = payload.users
|
|
||||||
const body = getPayloadwithChecksum(state.payload)
|
|
||||||
if (!body.hash) {
|
|
||||||
// toast.warn("Something is wrong... ", {
|
|
||||||
// toastId: "st_wrong",
|
|
||||||
// theme: "colored",
|
|
||||||
// })
|
|
||||||
|
|
||||||
return state
|
export function setPlayer(newUsers: NewUsers): string | null {
|
||||||
}
|
|
||||||
hash = body.hash
|
|
||||||
state.hash = hash
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
return hash
|
|
||||||
},
|
|
||||||
setSetting: (settings) => {
|
|
||||||
let hash: string | null = null
|
let hash: string | null = null
|
||||||
set((state: State) => {
|
console.log(newUsers)
|
||||||
if (!state.payload?.game) return state
|
users.forEach((user, i) => {
|
||||||
Object.assign(state.payload.game, settings)
|
const newUser = newUsers[i]
|
||||||
const body = getPayloadwithChecksum(state.payload)
|
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)
|
||||||
|
user.setHits(newUser.hits)
|
||||||
|
})
|
||||||
|
const body = getPayloadwithChecksum(getPayloadFromProps())
|
||||||
if (!body.hash) {
|
if (!body.hash) {
|
||||||
|
console.log("Something is wrong... ")
|
||||||
// toast.warn("Something is wrong... ", {
|
// toast.warn("Something is wrong... ", {
|
||||||
// toastId: "st_wrong",
|
// toastId: "st_wrong",
|
||||||
// theme: "colored",
|
// theme: "colored",
|
||||||
// })
|
// })
|
||||||
return state
|
return null
|
||||||
}
|
}
|
||||||
hash = body.hash
|
hash = body.hash
|
||||||
state.hash = hash
|
setHash(hash)
|
||||||
return state
|
|
||||||
})
|
|
||||||
return hash
|
return hash
|
||||||
},
|
}
|
||||||
full: (newGameProps) =>
|
export function setSetting(newSettings: GameSettings): string | null {
|
||||||
// eslint-disable-next-line solid/reactivity
|
let hash: string | null = null
|
||||||
set((state) => {
|
setAllowChat((e) => newSettings.allowChat ?? e)
|
||||||
if (state.hash === newGameProps.hash) {
|
setAllowMarkDraw((e) => newSettings.allowMarkDraw ?? e)
|
||||||
|
setAllowSpecials((e) => newSettings.allowSpecials ?? e)
|
||||||
|
setallowSpectators((e) => newSettings.allowSpectators ?? e)
|
||||||
|
const body = getPayloadwithChecksum(getPayloadFromProps())
|
||||||
|
if (!body.hash) {
|
||||||
|
console.log("Something is wrong... ")
|
||||||
|
// toast.warn("Something is wrong... ", {
|
||||||
|
// toastId: "st_wrong",
|
||||||
|
// theme: "colored",
|
||||||
|
// })
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
hash = body.hash
|
||||||
|
setHash(hash)
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setGameSetting(newSettings: GameSettings) {
|
||||||
|
return () => {
|
||||||
|
const hash = setSetting(newSettings)
|
||||||
|
socket.emit("gameSetting", newSettings, (newHash) => {
|
||||||
|
if (newHash === hash) return
|
||||||
|
console.log("hash", hash, newHash)
|
||||||
|
socket.emit("update", full)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function full(newProps: GamePropsSchema) {
|
||||||
|
if (hash() === newProps.hash) {
|
||||||
console.log("Everything up to date.")
|
console.log("Everything up to date.")
|
||||||
} else {
|
} else {
|
||||||
console.log("Update was needed.", state.hash, newGameProps.hash)
|
console.log("Update was needed.", hash(), newProps.hash)
|
||||||
|
|
||||||
if (
|
if (gameId() !== newProps.payload?.game?.id)
|
||||||
state.payload?.game?.id &&
|
|
||||||
state.payload?.game?.id !== newGameProps.payload?.game?.id
|
|
||||||
) {
|
|
||||||
console.warn(
|
console.warn(
|
||||||
"Different gameId detected on update: ",
|
"Different gameId detected on update: ",
|
||||||
state.payload?.game?.id,
|
gameId(),
|
||||||
newGameProps.payload?.game?.id,
|
newProps.payload?.game?.id,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
return newGameProps
|
setHash(newProps.hash)
|
||||||
|
setActiveIndex(newProps.payload.activeIndex)
|
||||||
|
setGamePin(newProps.payload.gamePin)
|
||||||
|
setGameId(newProps.payload.game?.id ?? "")
|
||||||
|
setGameState(newProps.payload.game?.state ?? "unknown")
|
||||||
|
setAllowChat(newProps.payload.game?.allowChat ?? false)
|
||||||
|
setAllowMarkDraw(newProps.payload.game?.allowMarkDraw ?? false)
|
||||||
|
setAllowSpecials(newProps.payload.game?.allowSpecials ?? false)
|
||||||
|
setallowSpectators(newProps.payload.game?.allowSpectators ?? false)
|
||||||
|
users.forEach((user, i) => {
|
||||||
|
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)
|
||||||
|
user.setHits(newUser.hits)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return state
|
}
|
||||||
}),
|
export function leave(cb: () => void) {
|
||||||
leave: (cb) => {
|
|
||||||
socket.emit("leave", (ack) => {
|
socket.emit("leave", (ack) => {
|
||||||
if (!ack) {
|
if (!ack) {
|
||||||
|
console.log("Something is wrong... ")
|
||||||
// toast.error("Something is wrong...")
|
// toast.error("Something is wrong...")
|
||||||
}
|
}
|
||||||
cb()
|
cb()
|
||||||
})
|
})
|
||||||
},
|
}
|
||||||
setIsReady: ({ i, isReady }) =>
|
export function setIsReadyFor({ i, isReady }: { i: 0 | 1; isReady: boolean }) {
|
||||||
set((state: State) => {
|
users[i].setIsReady(isReady)
|
||||||
state.userStates[i].setIsReady(isReady)
|
users[i].setIsConnected(true)
|
||||||
state.userStates[i].setIsConnected(true)
|
}
|
||||||
return state
|
export function newGameState(newState: GameState) {
|
||||||
}),
|
setGameState(newState)
|
||||||
gameState: (newState: GameState) =>
|
users.forEach((e) => e.setIsReady(false))
|
||||||
set((state: State) => {
|
}
|
||||||
if (!state.payload?.game) return state
|
export function setIsConnectedFor({
|
||||||
state.payload.game.state = newState
|
i,
|
||||||
state.userStates.forEach((e) => {
|
isConnected,
|
||||||
e.setIsReady(false)
|
}: {
|
||||||
})
|
i: 0 | 1
|
||||||
return state
|
isConnected: boolean
|
||||||
}),
|
}) {
|
||||||
setIsConnected: ({ i, isConnected }) =>
|
users[i].setIsConnected(isConnected)
|
||||||
set((state: State) => {
|
if (isConnected) return
|
||||||
state.userStates[i].setIsConnected(isConnected)
|
users[i].setIsReady(false)
|
||||||
if (!isConnected) state.userStates[i].setIsReady(false)
|
}
|
||||||
return state
|
|
||||||
}),
|
export function reset() {
|
||||||
reset: () => {
|
setHash(null)
|
||||||
set(initialState)
|
setActiveIndex(0)
|
||||||
},
|
setGamePin(null)
|
||||||
}))
|
setGameId("")
|
||||||
|
setGameState("unknown")
|
||||||
|
setallowSpectators(false)
|
||||||
|
setAllowSpecials(false)
|
||||||
|
setAllowChat(false)
|
||||||
|
setAllowMarkDraw(false)
|
||||||
|
setMenu("moves")
|
||||||
|
setMode(0)
|
||||||
|
setTarget(initlialTarget)
|
||||||
|
setTargetPreview(initlialTargetPreview)
|
||||||
|
setMouseCursor(initlialMouseCursor)
|
||||||
|
users.forEach(defaultUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultUser(user: ReturnType<typeof initialUser>) {
|
||||||
|
user.setIsReady(false)
|
||||||
|
user.setIsConnected(false)
|
||||||
|
user.setId("")
|
||||||
|
user.setName("")
|
||||||
|
user.setChats([])
|
||||||
|
user.setMoves([])
|
||||||
|
user.setShips([])
|
||||||
|
user.setHits([])
|
||||||
|
}
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
import { useSession } from "~/hooks/useSession"
|
|
||||||
|
|
||||||
import { useGameProps } from "./useGameProps"
|
|
||||||
|
|
||||||
function useIndex() {
|
|
||||||
const { payload } = useGameProps()
|
|
||||||
const session = useSession()
|
|
||||||
|
|
||||||
const selfIndex =
|
|
||||||
payload?.users.findIndex((e) => e?.id === session.latest?.user.id) ?? -1
|
|
||||||
const activeIndex = payload?.activeIndex ?? -1
|
|
||||||
const isActiveIndex = selfIndex >= 0 && payload?.activeIndex === selfIndex
|
|
||||||
const selfUser = payload?.users[selfIndex]
|
|
||||||
const activeUser = payload?.users[activeIndex === 0 ? 1 : 0]
|
|
||||||
|
|
||||||
return {
|
|
||||||
selfIndex,
|
|
||||||
activeIndex,
|
|
||||||
isActiveIndex,
|
|
||||||
selfUser,
|
|
||||||
activeUser,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default useIndex
|
|
|
@ -1,12 +0,0 @@
|
||||||
import { getSession } from "@auth/solid-start"
|
|
||||||
import { createServerData$ } from "solid-start/server"
|
|
||||||
import { authOptions } from "~/server/auth"
|
|
||||||
|
|
||||||
export const useSession = () => {
|
|
||||||
return createServerData$(
|
|
||||||
async (_, { request }) => {
|
|
||||||
return await getSession(request, authOptions)
|
|
||||||
},
|
|
||||||
{ key: () => ["auth_user"] },
|
|
||||||
)
|
|
||||||
}
|
|
77
leaky-ships/src/hooks/useSession.tsx
Normal file
77
leaky-ships/src/hooks/useSession.tsx
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import { Session } from "@auth/core/types"
|
||||||
|
import { getSession } from "@auth/solid-start"
|
||||||
|
import {
|
||||||
|
Accessor,
|
||||||
|
JSX,
|
||||||
|
createContext,
|
||||||
|
createSignal,
|
||||||
|
useContext,
|
||||||
|
} from "solid-js"
|
||||||
|
import { createServerData$ } from "solid-start/server"
|
||||||
|
import { ShipProps } from "~/interfaces/frontend"
|
||||||
|
import { initialUser } from "~/lib/utils/helpers"
|
||||||
|
import { authOptions } from "~/server/auth"
|
||||||
|
import { activeIndex, users } from "./useGameProps"
|
||||||
|
|
||||||
|
interface Concext {
|
||||||
|
session: Accessor<Session | null>
|
||||||
|
selfIndex: () => 0 | 1 | -1
|
||||||
|
activeIndex: Accessor<0 | 1>
|
||||||
|
isActiveIndex: () => boolean
|
||||||
|
selfUser: () => ReturnType<typeof initialUser> | null
|
||||||
|
activeUser: () => ReturnType<typeof initialUser>
|
||||||
|
ships: () => ShipProps[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const [state, setState] = createSignal<Session | null>(null)
|
||||||
|
const selfIndex = () => {
|
||||||
|
switch (state()?.user?.id) {
|
||||||
|
case users[0].id():
|
||||||
|
return 0
|
||||||
|
case users[1].id():
|
||||||
|
return 1
|
||||||
|
default:
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isActiveIndex = () => {
|
||||||
|
const sI = selfIndex()
|
||||||
|
return sI >= 0 && activeIndex() === sI
|
||||||
|
}
|
||||||
|
const selfUser = () => {
|
||||||
|
const i = selfIndex()
|
||||||
|
if (i === -1) return null
|
||||||
|
return users[i]
|
||||||
|
}
|
||||||
|
const activeUser = () => users[activeIndex() === 0 ? 1 : 0]
|
||||||
|
|
||||||
|
const ships = () => selfUser()?.ships() ?? []
|
||||||
|
const contextValue = {
|
||||||
|
session: state,
|
||||||
|
selfIndex,
|
||||||
|
activeIndex,
|
||||||
|
isActiveIndex,
|
||||||
|
selfUser,
|
||||||
|
activeUser,
|
||||||
|
ships,
|
||||||
|
}
|
||||||
|
export const SessionCtx = createContext<Concext>(contextValue)
|
||||||
|
|
||||||
|
export function SessionProvider(props: { children: JSX.Element }) {
|
||||||
|
const session = createServerData$(
|
||||||
|
async (_, { request }) => {
|
||||||
|
return await getSession(request, authOptions)
|
||||||
|
},
|
||||||
|
{ key: () => ["auth_user"] },
|
||||||
|
)()
|
||||||
|
setState(session ?? null)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SessionCtx.Provider value={contextValue}>
|
||||||
|
{props.children}
|
||||||
|
</SessionCtx.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSession = () => useContext(SessionCtx)
|
|
@ -1,17 +0,0 @@
|
||||||
import { ShipProps } from "../interfaces/frontend"
|
|
||||||
import { useGameProps } from "./useGameProps"
|
|
||||||
import useIndex from "./useIndex"
|
|
||||||
|
|
||||||
function useShips() {
|
|
||||||
const gameProps = useGameProps()
|
|
||||||
const { selfIndex } = useIndex()
|
|
||||||
|
|
||||||
const ships = () =>
|
|
||||||
gameProps.payload?.users.find((e) => e?.index === selfIndex)?.ships ?? []
|
|
||||||
const setShips = (ships: ShipProps[]) => gameProps.setShips(ships, selfIndex)
|
|
||||||
const removeShip = (ship: ShipProps) => gameProps.removeShip(ship, selfIndex)
|
|
||||||
|
|
||||||
return { ships, setShips, removeShip }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default useShips
|
|
|
@ -6,35 +6,41 @@ import { socket } from "~/lib/socket"
|
||||||
import { GamePropsSchema } from "~/lib/zodSchemas"
|
import { GamePropsSchema } 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 { useGameProps } from "./useGameProps"
|
import {
|
||||||
import useIndex from "./useIndex"
|
DispatchMove,
|
||||||
|
full,
|
||||||
|
gameId,
|
||||||
|
gameState,
|
||||||
|
setActiveIndex,
|
||||||
|
setIsConnectedFor,
|
||||||
|
setIsReadyFor,
|
||||||
|
setMenu,
|
||||||
|
setMode,
|
||||||
|
setPlayer,
|
||||||
|
setSetting,
|
||||||
|
setShips,
|
||||||
|
users,
|
||||||
|
} from "./useGameProps"
|
||||||
|
import { useSession } from "./useSession"
|
||||||
|
|
||||||
/** This function should only be called once per page, otherwise there will be multiple socket connections and duplicate event listeners. */
|
/** This function should only be called once per page, otherwise there will be multiple socket connections and duplicate event listeners. */
|
||||||
function useSocket() {
|
function useSocket() {
|
||||||
const [isConnectedState, setIsConnectedState] = createSignal(false)
|
const [isConnectedState, setIsConnectedState] = createSignal(false)
|
||||||
const { selfIndex } = useIndex()
|
const { selfIndex } = useSession()
|
||||||
const {
|
|
||||||
payload,
|
|
||||||
userStates,
|
|
||||||
setPlayer,
|
|
||||||
setSetting,
|
|
||||||
full,
|
|
||||||
setIsReady,
|
|
||||||
gameState,
|
|
||||||
setIsConnected,
|
|
||||||
setActiveIndex,
|
|
||||||
DispatchMove,
|
|
||||||
setShips,
|
|
||||||
} = useGameProps()
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const isConnected = () =>
|
const isConnected = () => {
|
||||||
selfIndex >= 0 ? userStates[selfIndex].isConnected() : isConnectedState()
|
const i = selfIndex()
|
||||||
|
return i !== -1
|
||||||
|
? users[i].isConnected() && isConnectedState()
|
||||||
|
: isConnectedState()
|
||||||
|
}
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (selfIndex < 0) return
|
const i = selfIndex()
|
||||||
setIsConnected({
|
if (i === -1) return
|
||||||
i: selfIndex,
|
setIsConnectedFor({
|
||||||
|
i,
|
||||||
isConnected: isConnectedState(),
|
isConnected: isConnectedState(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -63,39 +69,51 @@ function useSocket() {
|
||||||
|
|
||||||
const playerEvent = (event: PlayerEvent) => {
|
const playerEvent = (event: PlayerEvent) => {
|
||||||
const { type, i } = event
|
const { type, i } = event
|
||||||
// let message: string
|
let message: string
|
||||||
console.log("playerEvent", type)
|
console.log("playerEvent", type)
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "disconnect":
|
case "disconnect":
|
||||||
setIsConnected({
|
setIsConnectedFor({
|
||||||
i,
|
i,
|
||||||
isConnected: false,
|
isConnected: false,
|
||||||
})
|
})
|
||||||
// message = "Player is disconnected."
|
message = "Player is disconnected."
|
||||||
break
|
break
|
||||||
|
|
||||||
case "leave":
|
case "leave":
|
||||||
// message = "Player has left the lobby."
|
message = "Player has left the lobby."
|
||||||
break
|
break
|
||||||
|
|
||||||
case "connect":
|
case "connect":
|
||||||
setIsConnected({
|
setIsConnectedFor({
|
||||||
i,
|
i,
|
||||||
isConnected: true,
|
isConnected: true,
|
||||||
})
|
})
|
||||||
socket.emit("isReady", userStates[selfIndex].isReady())
|
const index = selfIndex()
|
||||||
// message = "Player has joined the lobby."
|
if (index !== -1) socket.emit("isReady", users[index].isReady())
|
||||||
|
message = "Player has joined the lobby."
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// message = "Not defined yet."
|
message = "Not defined yet."
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// toast.info(message, { toastId: message })
|
// toast.info(message, { toastId: message })
|
||||||
|
console.log(message)
|
||||||
if (type === "disconnect") return
|
if (type === "disconnect") return
|
||||||
const { payload, hash } = event
|
|
||||||
const newHash = setPlayer(payload)
|
const { hash } = event
|
||||||
console.log(newHash, hash, !newHash, newHash === hash)
|
const newHash = setPlayer(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 gameSetting = (newSettings: GameSettings, hash: string) => {
|
||||||
|
const newHash = setSetting(newSettings)
|
||||||
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) => {
|
||||||
|
@ -104,17 +122,16 @@ function useSocket() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const gameSetting = (payload: GameSettings, hash: string) => {
|
const activeIndex = (i: 0 | 1) => {
|
||||||
const newHash = setSetting(payload)
|
setActiveIndex(i)
|
||||||
if (!newHash || newHash === hash) return
|
if (i === selfIndex()) {
|
||||||
console.log("hash", hash, newHash)
|
setMenu("moves")
|
||||||
socket.emit("update", (body) => {
|
setMode(0)
|
||||||
console.log("update")
|
} else {
|
||||||
full(body)
|
setMenu("main")
|
||||||
})
|
setMode(-1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeIndex = (i: number) => setActiveIndex(i, selfIndex)
|
|
||||||
|
|
||||||
const disconnect = () => {
|
const disconnect = () => {
|
||||||
console.log("disconnect")
|
console.log("disconnect")
|
||||||
|
@ -125,7 +142,7 @@ function useSocket() {
|
||||||
socket.on("connect_error", connectError)
|
socket.on("connect_error", connectError)
|
||||||
socket.on("gameSetting", gameSetting)
|
socket.on("gameSetting", gameSetting)
|
||||||
socket.on("playerEvent", playerEvent)
|
socket.on("playerEvent", playerEvent)
|
||||||
socket.on("isReady", setIsReady)
|
socket.on("isReady", setIsReadyFor)
|
||||||
socket.on("gameState", gameState)
|
socket.on("gameState", gameState)
|
||||||
socket.on("dispatchMove", DispatchMove)
|
socket.on("dispatchMove", DispatchMove)
|
||||||
socket.on("activeIndex", activeIndex)
|
socket.on("activeIndex", activeIndex)
|
||||||
|
@ -137,7 +154,7 @@ function useSocket() {
|
||||||
socket.off("connect_error", connectError)
|
socket.off("connect_error", connectError)
|
||||||
socket.off("gameSetting", gameSetting)
|
socket.off("gameSetting", gameSetting)
|
||||||
socket.off("playerEvent", playerEvent)
|
socket.off("playerEvent", playerEvent)
|
||||||
socket.off("isReady", setIsReady)
|
socket.off("isReady", setIsReadyFor)
|
||||||
socket.off("gameState", gameState)
|
socket.off("gameState", gameState)
|
||||||
socket.off("dispatchMove", DispatchMove)
|
socket.off("dispatchMove", DispatchMove)
|
||||||
socket.off("activeIndex", activeIndex)
|
socket.off("activeIndex", activeIndex)
|
||||||
|
@ -147,7 +164,7 @@ function useSocket() {
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!payload?.game?.id) {
|
if (!gameId()) {
|
||||||
socket.disconnect()
|
socket.disconnect()
|
||||||
fetch("/api/game/running", {
|
fetch("/api/game/running", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
@ -168,8 +185,7 @@ function useSocket() {
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isConnected:
|
isConnected,
|
||||||
selfIndex >= 0 ? userStates[selfIndex].isConnected : isConnectedState,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { Session } from "@auth/core/types"
|
import { Session } from "@auth/core/types"
|
||||||
import type { Server as HTTPServer } from "http"
|
import http from "http"
|
||||||
import type { Socket as NetSocket } from "net"
|
|
||||||
import type {
|
import type {
|
||||||
Server as IOServer,
|
Server as IOServer,
|
||||||
Server,
|
Server,
|
||||||
|
@ -17,33 +16,25 @@ import {
|
||||||
ShipProps,
|
ShipProps,
|
||||||
} from "./frontend"
|
} from "./frontend"
|
||||||
|
|
||||||
interface SocketServer extends HTTPServer {
|
export interface SocketServer extends http.Server {
|
||||||
io?: IOServer
|
io?: IOServer
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SocketWithIO extends NetSocket {
|
|
||||||
server: SocketServer
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ResponseWithSocket extends Response {
|
|
||||||
socket: SocketWithIO
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ServerToClientEvents {
|
export interface ServerToClientEvents {
|
||||||
// noArg: () => void
|
// noArg: () => void
|
||||||
// basicEmit: (a: number, b: string, c: Buffer) => void
|
// basicEmit: (a: number, b: string, c: Buffer) => void
|
||||||
// withAck: (d: string, ) => void
|
// withAck: (d: string, ) => void
|
||||||
gameSetting: (payload: GameSettings, hash: string) => void
|
gameSetting: (payload: GameSettings, hash: string) => void
|
||||||
playerEvent: (event: PlayerEvent) => void
|
playerEvent: (event: PlayerEvent) => void
|
||||||
isReady: (payload: { i: number; isReady: boolean }) => void
|
isReady: (payload: { i: 0 | 1; isReady: boolean }) => void
|
||||||
isConnected: (payload: { i: number; 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: number) => void
|
||||||
"draw-line": (props: DrawLineProps, userIndex: number) => void
|
"draw-line": (props: DrawLineProps, userIndex: number) => 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: number) => void
|
||||||
activeIndex: (index: number) => void
|
activeIndex: (index: 0 | 1) => void
|
||||||
dispatchMove: (props: MoveDispatchProps, i: number) => void
|
dispatchMove: (props: MoveDispatchProps, i: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +66,7 @@ interface SocketData {
|
||||||
}
|
}
|
||||||
user: Session["user"]
|
user: Session["user"]
|
||||||
gameId: string
|
gameId: string
|
||||||
index: number
|
index: 0 | 1
|
||||||
}
|
}
|
||||||
|
|
||||||
export type sServer = Server<
|
export type sServer = Server<
|
|
@ -76,15 +76,16 @@ export type GameSettingKeys =
|
||||||
| "allowChat"
|
| "allowChat"
|
||||||
| "allowMarkDraw"
|
| "allowMarkDraw"
|
||||||
|
|
||||||
export type GameSettings = { [key in GameSettingKeys]?: boolean }
|
export type GameSettings = Partial<Record<GameSettingKeys, boolean>>
|
||||||
export type PlayerEvent =
|
export type PlayerEvent =
|
||||||
| {
|
| {
|
||||||
type: "connect" | "leave"
|
type: "connect" | "leave"
|
||||||
i: number
|
i: 0 | 1
|
||||||
payload: { users: PlayerSchema[] }
|
users: NewUsers
|
||||||
hash: string
|
hash: string
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "disconnect"
|
type: "disconnect"
|
||||||
i: number
|
i: 0 | 1
|
||||||
}
|
}
|
||||||
|
export type NewUsers = { 0: PlayerSchema | null; 1: PlayerSchema | null }
|
||||||
|
|
|
@ -8,7 +8,7 @@ const pinBodySchema = z.object({
|
||||||
|
|
||||||
async function getPinFromBody(request: APIEvent["request"]) {
|
async function getPinFromBody(request: APIEvent["request"]) {
|
||||||
try {
|
try {
|
||||||
const body = request.json()
|
const body = await request.json()
|
||||||
const { pin } = pinBodySchema.parse(body)
|
const { pin } = pinBodySchema.parse(body)
|
||||||
return pin
|
return pin
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
@ -59,10 +59,8 @@ async function logging(
|
||||||
const xForwardedFor =
|
const xForwardedFor =
|
||||||
typeof request.headers.get === "function"
|
typeof request.headers.get === "function"
|
||||||
? request.headers.get("x-forwarded-for")
|
? request.headers.get("x-forwarded-for")
|
||||||
: "0.0.0.0" // request.headers
|
: // @ts-expect-error Bad IncomingHttpHeaders Type
|
||||||
if (typeof request.headers.get !== "function")
|
request.headers["x-forwarded-for"]
|
||||||
console.log("IncomingHttpHeaders", request.headers)
|
|
||||||
// ("x-forwarded-for")
|
|
||||||
const ip = (xForwardedFor || "127.0.0.1, 192.168.178.1").split(",")
|
const ip = (xForwardedFor || "127.0.0.1, 192.168.178.1").split(",")
|
||||||
const route = request.url
|
const route = request.url
|
||||||
messages.console = [ip[0].yellow, route?.green, messages.console].join(
|
messages.console = [ip[0].yellow, route?.green, messages.console].join(
|
||||||
|
|
35
leaky-ships/src/lib/getPayloadFromProps.ts
Normal file
35
leaky-ships/src/lib/getPayloadFromProps.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import {
|
||||||
|
activeIndex,
|
||||||
|
allowChat,
|
||||||
|
allowMarkDraw,
|
||||||
|
allowSpecials,
|
||||||
|
allowSpectators,
|
||||||
|
gameId,
|
||||||
|
gamePin,
|
||||||
|
gameState,
|
||||||
|
users,
|
||||||
|
} from "~/hooks/useGameProps"
|
||||||
|
|
||||||
|
export function getPayloadFromProps() {
|
||||||
|
return {
|
||||||
|
game: {
|
||||||
|
id: gameId(),
|
||||||
|
state: gameState(),
|
||||||
|
allowChat: allowChat(),
|
||||||
|
allowMarkDraw: allowMarkDraw(),
|
||||||
|
allowSpecials: allowSpecials(),
|
||||||
|
allowSpectators: allowSpectators(),
|
||||||
|
},
|
||||||
|
gamePin: gamePin(),
|
||||||
|
users: users.map((user, i) => ({
|
||||||
|
index: i,
|
||||||
|
id: user.id(),
|
||||||
|
name: user.name(),
|
||||||
|
chats: user.chats(),
|
||||||
|
moves: user.moves(),
|
||||||
|
ships: user.ships(),
|
||||||
|
hits: user.hits(),
|
||||||
|
})),
|
||||||
|
activeIndex: activeIndex(),
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,6 @@
|
||||||
import crypto from "crypto"
|
import hash from "object-hash"
|
||||||
import { GamePropsSchema } from "./zodSchemas"
|
import { GamePropsSchema } from "./zodSchemas"
|
||||||
|
|
||||||
export function getPayloadwithChecksum(
|
export const getPayloadwithChecksum = (
|
||||||
payload: GamePropsSchema["payload"],
|
payload: GamePropsSchema["payload"],
|
||||||
): GamePropsSchema {
|
): GamePropsSchema => ({ payload, hash: hash(payload) })
|
||||||
const objString = JSON.stringify(payload)
|
|
||||||
const hash = crypto.createHash("md5").update(objString).digest("hex")
|
|
||||||
return { payload, hash }
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { io } from "socket.io-client"
|
import { io } from "socket.io-client"
|
||||||
import { cSocket } from "../interfaces/NextApiSocket"
|
import { cSocket } from "../interfaces/ApiSocket"
|
||||||
|
|
||||||
export const socket: cSocket = io({
|
export const socket: cSocket = io({
|
||||||
path: "/api/ws",
|
path: "/api/ws",
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { createSignal } from "solid-js"
|
||||||
import { count } from "~/components/Gamefield/Gamefield"
|
import { count } from "~/components/Gamefield/Gamefield"
|
||||||
import type {
|
import type {
|
||||||
Hit,
|
Hit,
|
||||||
|
@ -10,7 +11,7 @@ import type {
|
||||||
TargetList,
|
TargetList,
|
||||||
TargetPreview,
|
TargetPreview,
|
||||||
} from "../../interfaces/frontend"
|
} from "../../interfaces/frontend"
|
||||||
import { Orientation } from "../zodSchemas"
|
import { ChatSchema, MoveSchema, 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"
|
||||||
|
@ -129,6 +130,34 @@ 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[]>([])
|
||||||
|
const [hits, setHits] = createSignal<Hit[]>([])
|
||||||
|
return {
|
||||||
|
isReady,
|
||||||
|
setIsReady,
|
||||||
|
isConnected,
|
||||||
|
setIsConnected,
|
||||||
|
id,
|
||||||
|
setId,
|
||||||
|
name,
|
||||||
|
setName,
|
||||||
|
chats,
|
||||||
|
setChats,
|
||||||
|
moves,
|
||||||
|
setMoves,
|
||||||
|
ships,
|
||||||
|
setShips,
|
||||||
|
hits,
|
||||||
|
setHits,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const shipProps = (
|
export const shipProps = (
|
||||||
ships: ShipProps[],
|
ships: ShipProps[],
|
||||||
|
|
|
@ -34,44 +34,52 @@ export const movesSchema = createSelectSchema(moves)
|
||||||
export const chatsSchema = createSelectSchema(chats)
|
export const chatsSchema = createSelectSchema(chats)
|
||||||
export const user_gamesSchema = createSelectSchema(user_games)
|
export const user_gamesSchema = createSelectSchema(user_games)
|
||||||
|
|
||||||
export const PlayerSchema = z
|
export const ChatSchema = z.object({
|
||||||
.object({
|
|
||||||
id: z.string(),
|
|
||||||
name: z.string().nullable(),
|
|
||||||
index: z.number(),
|
|
||||||
chats: z
|
|
||||||
.object({
|
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
event: z.string().nullable(),
|
event: z.string().nullable(),
|
||||||
message: z.string().nullable(),
|
message: z.string().nullable(),
|
||||||
createdAt: z.coerce.date(),
|
createdAt: z.coerce.date(),
|
||||||
})
|
})
|
||||||
.array(),
|
|
||||||
moves: z
|
export type ChatSchema = z.infer<typeof ChatSchema>
|
||||||
.object({
|
|
||||||
|
export const MoveSchema = z.object({
|
||||||
index: z.number(),
|
index: z.number(),
|
||||||
type: MoveType,
|
type: MoveType,
|
||||||
x: z.number(),
|
x: z.number(),
|
||||||
y: z.number(),
|
y: z.number(),
|
||||||
orientation: Orientation,
|
orientation: Orientation,
|
||||||
})
|
})
|
||||||
.array(),
|
|
||||||
ships: z
|
export type MoveSchema = z.infer<typeof MoveSchema>
|
||||||
.object({
|
|
||||||
|
export const ShipShema = z.object({
|
||||||
size: z.number(),
|
size: z.number(),
|
||||||
variant: z.number(),
|
variant: z.number(),
|
||||||
x: z.number(),
|
x: z.number(),
|
||||||
y: z.number(),
|
y: z.number(),
|
||||||
orientation: Orientation,
|
orientation: Orientation,
|
||||||
})
|
})
|
||||||
.array(),
|
|
||||||
hits: z
|
export type ShipShema = z.infer<typeof ShipShema>
|
||||||
.object({
|
|
||||||
|
export const HitSchema = z.object({
|
||||||
x: z.number(),
|
x: z.number(),
|
||||||
y: z.number(),
|
y: z.number(),
|
||||||
hit: z.boolean(),
|
hit: z.boolean(),
|
||||||
})
|
})
|
||||||
.array(),
|
|
||||||
|
export type HitSchema = z.infer<typeof HitSchema>
|
||||||
|
|
||||||
|
export const PlayerSchema = z
|
||||||
|
.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
index: z.number(),
|
||||||
|
chats: ChatSchema.array(),
|
||||||
|
moves: MoveSchema.array(),
|
||||||
|
ships: ShipShema.array(),
|
||||||
|
hits: HitSchema.array(),
|
||||||
})
|
})
|
||||||
.nullable()
|
.nullable()
|
||||||
|
|
||||||
|
@ -89,8 +97,8 @@ export const CreateSchema = z.object({
|
||||||
})
|
})
|
||||||
.nullable(),
|
.nullable(),
|
||||||
gamePin: z.string().nullable(),
|
gamePin: z.string().nullable(),
|
||||||
users: PlayerSchema.array(),
|
users: z.object({ 0: PlayerSchema, 1: PlayerSchema }),
|
||||||
activeIndex: z.number().optional(),
|
activeIndex: z.literal(0).or(z.literal(1)),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const GamePropsSchema = z.object({
|
export const GamePropsSchema = z.object({
|
||||||
|
@ -102,5 +110,6 @@ export const optionalGamePropsSchema = z.object({
|
||||||
hash: z.string().nullable(),
|
hash: z.string().nullable(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export type CreateSchema = z.infer<typeof CreateSchema>
|
||||||
export type GamePropsSchema = z.infer<typeof GamePropsSchema>
|
export type GamePropsSchema = z.infer<typeof GamePropsSchema>
|
||||||
export type optionalGamePropsSchema = z.infer<typeof optionalGamePropsSchema>
|
export type optionalGamePropsSchema = z.infer<typeof optionalGamePropsSchema>
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
Scripts,
|
Scripts,
|
||||||
Title,
|
Title,
|
||||||
} from "solid-start"
|
} from "solid-start"
|
||||||
|
import { SessionProvider } from "./hooks/useSession"
|
||||||
import "./styles/App.scss"
|
import "./styles/App.scss"
|
||||||
import "./styles/globals.scss"
|
import "./styles/globals.scss"
|
||||||
import "./styles/grid.scss"
|
import "./styles/grid.scss"
|
||||||
|
@ -41,9 +42,11 @@ export default function Root() {
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<Suspense fallback={<div>Loading</div>}>
|
<Suspense fallback={<div>Loading</div>}>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
|
<SessionProvider>
|
||||||
<Routes>
|
<Routes>
|
||||||
<FileRoutes />
|
<FileRoutes />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
</SessionProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
<Scripts />
|
<Scripts />
|
||||||
|
|
5
leaky-ships/src/routes/[...404].tsx
Normal file
5
leaky-ships/src/routes/[...404].tsx
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { redirect } from "solid-start"
|
||||||
|
|
||||||
|
export function GET() {
|
||||||
|
return redirect("/")
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { getSession } from "@auth/solid-start"
|
import { getSession } from "@auth/solid-start"
|
||||||
|
import { createId } from "@paralleldrive/cuid2"
|
||||||
import { eq } from "drizzle-orm"
|
import { eq } from "drizzle-orm"
|
||||||
import { APIEvent } from "solid-start"
|
import { APIEvent } from "solid-start"
|
||||||
import db from "~/drizzle"
|
import db from "~/drizzle"
|
||||||
|
@ -30,17 +31,35 @@ export async function POST({ request }: APIEvent) {
|
||||||
message: "Running game already exists.",
|
message: "Running game already exists.",
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const gameId = (await db.insert(games).values({}).returning())[0].id
|
const gameId = (
|
||||||
|
await db
|
||||||
|
.insert(games)
|
||||||
|
.values({
|
||||||
|
id: createId(),
|
||||||
|
})
|
||||||
|
.returning()
|
||||||
|
)[0].id
|
||||||
const user_Game = (
|
const user_Game = (
|
||||||
await db
|
await db
|
||||||
.insert(user_games)
|
.insert(user_games)
|
||||||
.values({ gameId, userId: id, index: 0 })
|
.values({
|
||||||
|
id: createId(),
|
||||||
|
gameId,
|
||||||
|
userId: id,
|
||||||
|
index: 0,
|
||||||
|
})
|
||||||
.returning()
|
.returning()
|
||||||
)[0]
|
)[0]
|
||||||
await db.insert(gamepins).values({ gameId, pin })
|
await db.insert(gamepins).values({
|
||||||
await db
|
id: createId(),
|
||||||
.insert(chats)
|
gameId,
|
||||||
.values({ user_game_id: user_Game.id, event: "created" })
|
pin,
|
||||||
|
})
|
||||||
|
await db.insert(chats).values({
|
||||||
|
id: createId(),
|
||||||
|
user_game_id: user_Game.id,
|
||||||
|
event: "created",
|
||||||
|
})
|
||||||
game = await db.query.games.findFirst({
|
game = await db.query.games.findFirst({
|
||||||
where: eq(games.id, gameId),
|
where: eq(games.id, gameId),
|
||||||
...gameSelects,
|
...gameSelects,
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { getSession } from "@auth/solid-start"
|
import { getSession } from "@auth/solid-start"
|
||||||
import { and, eq, exists, inArray, ne } from "drizzle-orm"
|
import { createId } from "@paralleldrive/cuid2"
|
||||||
|
import { and, eq } from "drizzle-orm"
|
||||||
import { APIEvent } from "solid-start"
|
import { APIEvent } from "solid-start"
|
||||||
import db from "~/drizzle"
|
import db from "~/drizzle"
|
||||||
import { user_games, users } from "~/drizzle/schemas/Tables"
|
import { user_games } from "~/drizzle/schemas/Tables"
|
||||||
import { rejectionErrors } from "~/lib/backend/errors"
|
import { rejectionErrors } from "~/lib/backend/errors"
|
||||||
import getPinFromBody from "~/lib/backend/getPinFromBody"
|
import getPinFromBody from "~/lib/backend/getPinFromBody"
|
||||||
import logging from "~/lib/backend/logging"
|
import logging from "~/lib/backend/logging"
|
||||||
|
@ -34,28 +35,15 @@ export async function POST({ request }: APIEvent) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let game = await db.query.games.findFirst({
|
const user_Game = await db.query.user_games.findFirst({
|
||||||
where: (game) =>
|
where: and(
|
||||||
and(
|
eq(user_games.userId, id),
|
||||||
ne(game.state, "ended"),
|
eq(user_games.gameId, gamePin.gameId),
|
||||||
exists(
|
|
||||||
db
|
|
||||||
.select()
|
|
||||||
.from(user_games)
|
|
||||||
.where(
|
|
||||||
inArray(
|
|
||||||
user_games.userId,
|
|
||||||
db
|
|
||||||
.select({ data: users.id })
|
|
||||||
.from(users)
|
|
||||||
.where(eq(users.id, id)),
|
|
||||||
),
|
),
|
||||||
),
|
with: { game: gameSelects },
|
||||||
),
|
|
||||||
),
|
|
||||||
...gameSelects,
|
|
||||||
})
|
})
|
||||||
if (!game) {
|
|
||||||
|
if (user_Game) {
|
||||||
return sendResponse(request, {
|
return sendResponse(request, {
|
||||||
message: "Spieler ist bereits in Spiel!",
|
message: "Spieler ist bereits in Spiel!",
|
||||||
redirectUrl: "/api/game/running",
|
redirectUrl: "/api/game/running",
|
||||||
|
@ -63,18 +51,17 @@ export async function POST({ request }: APIEvent) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const gameId = (
|
|
||||||
await db
|
await db
|
||||||
.insert(user_games)
|
.insert(user_games)
|
||||||
.values({
|
.values({
|
||||||
gameId: game.id,
|
id: createId(),
|
||||||
|
gameId: gamePin.gameId,
|
||||||
userId: id,
|
userId: id,
|
||||||
index: 1,
|
index: 1,
|
||||||
})
|
})
|
||||||
.returning()
|
.returning()
|
||||||
)[0].gameId
|
|
||||||
|
|
||||||
game = await getGameById(gameId)
|
const game = await getGameById(gamePin.gameId)
|
||||||
|
|
||||||
if (!game) return
|
if (!game) return
|
||||||
|
|
||||||
|
@ -87,11 +74,8 @@ export async function POST({ request }: APIEvent) {
|
||||||
})
|
})
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
await logging(
|
await logging(err.code + err.meta + err.message, ["error"], request)
|
||||||
"HERE".red + err.code + err.meta + err.message,
|
console.log(err)
|
||||||
["error"],
|
|
||||||
request,
|
|
||||||
)
|
|
||||||
throw sendError(request, rejectionErrors.gameNotFound)
|
throw sendError(request, rejectionErrors.gameNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { getSession } from "@auth/solid-start"
|
||||||
import { and, eq, exists, ne } from "drizzle-orm"
|
import { and, eq, exists, ne } from "drizzle-orm"
|
||||||
import { APIEvent } from "solid-start/api"
|
import { APIEvent } from "solid-start/api"
|
||||||
import db from "~/drizzle"
|
import db from "~/drizzle"
|
||||||
import { games, user_games, users } from "~/drizzle/schemas/Tables"
|
import { games, user_games } from "~/drizzle/schemas/Tables"
|
||||||
import { rejectionErrors } from "~/lib/backend/errors"
|
import { rejectionErrors } from "~/lib/backend/errors"
|
||||||
import sendResponse from "~/lib/backend/sendResponse"
|
import sendResponse from "~/lib/backend/sendResponse"
|
||||||
import { getPayloadwithChecksum } from "~/lib/getPayloadwithChecksum"
|
import { getPayloadwithChecksum } from "~/lib/getPayloadwithChecksum"
|
||||||
|
@ -87,10 +87,7 @@ export const getRunningGameToUser = async (userId: string) => {
|
||||||
and(
|
and(
|
||||||
ne(game.state, "ended"),
|
ne(game.state, "ended"),
|
||||||
exists(
|
exists(
|
||||||
db
|
db.select().from(user_games).where(eq(user_games.userId, userId)),
|
||||||
.select()
|
|
||||||
.from(user_games)
|
|
||||||
.where(exists(db.select().from(users).where(eq(users.id, userId)))),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
...gameSelects,
|
...gameSelects,
|
||||||
|
@ -100,23 +97,41 @@ export const getRunningGameToUser = async (userId: string) => {
|
||||||
export function composeBody(
|
export function composeBody(
|
||||||
gameDB: NonNullable<Awaited<ReturnType<typeof getRunningGameToUser>>>,
|
gameDB: NonNullable<Awaited<ReturnType<typeof getRunningGameToUser>>>,
|
||||||
): GamePropsSchema {
|
): GamePropsSchema {
|
||||||
const { gamePin, ...game } = gameDB
|
const { gamePin, users, ...game } = gameDB
|
||||||
const users = gameDB.users
|
const mappedUsers = users.map(({ user, ...props }) => ({
|
||||||
.map(({ user, ...props }) => ({
|
|
||||||
...props,
|
...props,
|
||||||
...user,
|
...user,
|
||||||
}))
|
}))
|
||||||
.sort((user1, user2) => user1.index - user2.index)
|
const composedUsers = {
|
||||||
let activeIndex = undefined
|
0: mappedUsers.find((e) => e.index === 0) ?? {
|
||||||
|
index: 0,
|
||||||
|
id: "",
|
||||||
|
name: "",
|
||||||
|
chats: [],
|
||||||
|
moves: [],
|
||||||
|
ships: [],
|
||||||
|
hits: [],
|
||||||
|
},
|
||||||
|
1: mappedUsers.find((e) => e.index === 1) ?? {
|
||||||
|
index: 1,
|
||||||
|
id: "",
|
||||||
|
name: "",
|
||||||
|
chats: [],
|
||||||
|
moves: [],
|
||||||
|
ships: [],
|
||||||
|
hits: [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
let activeIndex: 0 | 1 = 0
|
||||||
if (game.state === "running") {
|
if (game.state === "running") {
|
||||||
const l1 = game.users[0].moves.length
|
const l1 = users[0]?.moves.length ?? 0
|
||||||
const l2 = game.users[1].moves.length
|
const l2 = users[1]?.moves.length ?? 0
|
||||||
activeIndex = l1 > l2 ? 1 : 0
|
activeIndex = l1 > l2 ? 1 : 0
|
||||||
}
|
}
|
||||||
const payload = {
|
const payload = {
|
||||||
game: game,
|
game: game,
|
||||||
gamePin: gamePin?.pin ?? null,
|
gamePin: gamePin?.pin ?? null,
|
||||||
users,
|
users: composedUsers,
|
||||||
activeIndex,
|
activeIndex,
|
||||||
}
|
}
|
||||||
return getPayloadwithChecksum(payload)
|
return getPayloadwithChecksum(payload)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { getSession } from "@auth/solid-start"
|
import { getSession } from "@auth/solid-start"
|
||||||
|
import { createId } from "@paralleldrive/cuid2"
|
||||||
import colors from "colors"
|
import colors from "colors"
|
||||||
import { and, eq } from "drizzle-orm"
|
import { and, eq } from "drizzle-orm"
|
||||||
import status from "http-status"
|
import status from "http-status"
|
||||||
|
@ -6,7 +7,7 @@ import { Server } from "socket.io"
|
||||||
import { APIEvent } from "solid-start"
|
import { APIEvent } from "solid-start"
|
||||||
import db from "~/drizzle"
|
import db from "~/drizzle"
|
||||||
import { games, moves, ships, user_games } from "~/drizzle/schemas/Tables"
|
import { games, moves, ships, user_games } from "~/drizzle/schemas/Tables"
|
||||||
import { ResponseWithSocket, sServer } from "~/interfaces/NextApiSocket"
|
import { SocketServer, sServer } from "~/interfaces/ApiSocket"
|
||||||
import logging from "~/lib/backend/logging"
|
import logging from "~/lib/backend/logging"
|
||||||
import { GamePropsSchema } from "~/lib/zodSchemas"
|
import { GamePropsSchema } from "~/lib/zodSchemas"
|
||||||
import { authOptions } from "~/server/auth"
|
import { authOptions } from "~/server/auth"
|
||||||
|
@ -19,27 +20,33 @@ import {
|
||||||
|
|
||||||
colors.enable()
|
colors.enable()
|
||||||
|
|
||||||
const res = new Response() as ResponseWithSocket
|
export async function GET({
|
||||||
|
request,
|
||||||
export async function GET({ request }: APIEvent) {
|
httpServer,
|
||||||
if (res.socket.server.io) {
|
}: APIEvent & { httpServer: SocketServer }) {
|
||||||
|
if (httpServer.io) {
|
||||||
logging("Socket is already running " + request.url, ["infoCyan"], request)
|
logging("Socket is already running " + request.url, ["infoCyan"], request)
|
||||||
} else {
|
} else {
|
||||||
logging("Socket is initializing " + request.url, ["infoCyan"], request)
|
logging("Socket is initializing " + request.url, ["infoCyan"], request)
|
||||||
const io: sServer = new Server(res.socket.server, {
|
const io: sServer = new Server(httpServer, {
|
||||||
path: "/api/ws",
|
path: "/api/ws",
|
||||||
cors: {
|
cors: {
|
||||||
origin: "https://leaky-ships.mal-noh.de",
|
origin: "https://leaky-ships.mal-noh.de",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
res.socket.server.io = io
|
httpServer.io = io
|
||||||
|
|
||||||
// io.use(authenticate)
|
// io.use(authenticate)
|
||||||
io.use(async (socket, next) => {
|
io.use(async (socket, next) => {
|
||||||
try {
|
try {
|
||||||
// @ts-expect-error TODO add correct server
|
const url = process.env.AUTH_URL! + socket.request.url
|
||||||
const session = await getSession(socket.request, authOptions)
|
const session = await getSession(
|
||||||
|
new Request(url, {
|
||||||
|
headers: socket.request.headers as Record<string, string>,
|
||||||
|
}),
|
||||||
|
authOptions,
|
||||||
|
)
|
||||||
if (!session) return next(new Error(status["401"]))
|
if (!session) return next(new Error(status["401"]))
|
||||||
socket.data.user = session.user
|
socket.data.user = session.user
|
||||||
|
|
||||||
|
@ -55,18 +62,15 @@ export async function GET({ request }: APIEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { payload, hash } = composeBody(game)
|
const { payload, hash } = composeBody(game)
|
||||||
// let index: number | null = null
|
const index = payload.users[0]?.id === socket.data.user?.id ? 0 : 1
|
||||||
const index = payload.users.findIndex(
|
if (index !== 0 && index !== 1) return next(new Error(status["401"]))
|
||||||
(user) => socket.data.user?.id === user?.id,
|
|
||||||
)
|
|
||||||
if (index < 0) return next(new Error(status["401"]))
|
|
||||||
socket.data.index = index
|
socket.data.index = index
|
||||||
socket.data.gameId = game.id
|
socket.data.gameId = game.id
|
||||||
socket.join(game.id)
|
socket.join(game.id)
|
||||||
socket.to(game.id).emit("playerEvent", {
|
socket.to(game.id).emit("playerEvent", {
|
||||||
type: "connect",
|
type: "connect",
|
||||||
i: socket.data.index,
|
i: socket.data.index,
|
||||||
payload: { users: payload.users },
|
users: payload.users,
|
||||||
hash,
|
hash,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -89,14 +93,16 @@ export async function GET({ request }: APIEvent) {
|
||||||
socket.on("update", async (cb) => {
|
socket.on("update", async (cb) => {
|
||||||
const game = await getGameById(socket.data.gameId ?? "")
|
const game = await getGameById(socket.data.gameId ?? "")
|
||||||
if (!game) return
|
if (!game) return
|
||||||
|
if (socket.data.index === 1 && game.users.length === 1)
|
||||||
|
socket.data.index = 0
|
||||||
const body = composeBody(game)
|
const body = composeBody(game)
|
||||||
cb(body)
|
cb(body)
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on("gameSetting", async (payload, cb) => {
|
socket.on("gameSetting", async (newSettings, cb) => {
|
||||||
const game = await db
|
const game = await db
|
||||||
.update(games)
|
.update(games)
|
||||||
.set(payload)
|
.set(newSettings)
|
||||||
.where(eq(games.id, socket.data.gameId))
|
.where(eq(games.id, socket.data.gameId))
|
||||||
.returning()
|
.returning()
|
||||||
.then((updatedGame) =>
|
.then((updatedGame) =>
|
||||||
|
@ -109,14 +115,14 @@ export async function GET({ request }: APIEvent) {
|
||||||
const { hash } = composeBody(game)
|
const { hash } = composeBody(game)
|
||||||
if (!hash) return
|
if (!hash) return
|
||||||
cb(hash)
|
cb(hash)
|
||||||
socket.to(game?.id).emit("gameSetting", payload, hash)
|
socket.to(game?.id).emit("gameSetting", newSettings, hash)
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on("ping", (callback) => callback())
|
socket.on("ping", (callback) => callback())
|
||||||
|
|
||||||
socket.on("leave", async (cb) => {
|
socket.on("leave", async (cb) => {
|
||||||
if (!socket.data.gameId || !socket.data.user?.id) return cb(false)
|
const user_Game = (
|
||||||
const user_Game = await db
|
await db
|
||||||
.delete(user_games)
|
.delete(user_games)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
|
@ -125,25 +131,25 @@ export async function GET({ request }: APIEvent) {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.returning()
|
.returning()
|
||||||
|
)[0]
|
||||||
if (!user_Game) return
|
if (!user_Game) return
|
||||||
const enemy = await db.query.user_games.findFirst({
|
const enemy = await db.query.user_games.findFirst({
|
||||||
where: eq(user_games.gameId, socket.data.gameId),
|
where: eq(user_games.gameId, socket.data.gameId),
|
||||||
})
|
})
|
||||||
let body: GamePropsSchema
|
let body: GamePropsSchema
|
||||||
if (user_Game[0].index === 1 && enemy) {
|
if (user_Game.index === 0 && enemy) {
|
||||||
const game = await db
|
const game = await db
|
||||||
.update(user_games)
|
.update(user_games)
|
||||||
.set({
|
.set({
|
||||||
index: 1,
|
index: 0,
|
||||||
})
|
})
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(user_games.gameId, socket.data.gameId),
|
eq(user_games.gameId, socket.data.gameId),
|
||||||
eq(user_games.index, 2),
|
eq(user_games.index, 1),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.returning()
|
.returning()
|
||||||
|
|
||||||
.then((user_Game) =>
|
.then((user_Game) =>
|
||||||
db.query.games.findFirst({
|
db.query.games.findFirst({
|
||||||
where: eq(games.id, user_Game[0].gameId),
|
where: eq(games.id, user_Game[0].gameId),
|
||||||
|
@ -161,17 +167,16 @@ export async function GET({ request }: APIEvent) {
|
||||||
body = composeBody(game)
|
body = composeBody(game)
|
||||||
}
|
}
|
||||||
const { payload, hash } = body
|
const { payload, hash } = body
|
||||||
if (!payload || !hash || socket.data.index === undefined)
|
|
||||||
return cb(false)
|
|
||||||
socket.to(socket.data.gameId).emit("playerEvent", {
|
socket.to(socket.data.gameId).emit("playerEvent", {
|
||||||
type: "leave",
|
type: "leave",
|
||||||
i: socket.data.index,
|
i: socket.data.index,
|
||||||
payload: { users: payload.users },
|
users: payload.users,
|
||||||
hash,
|
hash,
|
||||||
})
|
})
|
||||||
|
socket.data.gameId = ""
|
||||||
cb(true)
|
cb(true)
|
||||||
|
|
||||||
if (!payload.users.length) {
|
if (!payload.users[0] && !payload.users[1]) {
|
||||||
await db.delete(games).where(eq(games.id, socket.data.gameId))
|
await db.delete(games).where(eq(games.id, socket.data.gameId))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -239,10 +244,12 @@ export async function GET({ request }: APIEvent) {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!user_Game) return
|
if (!user_Game) return
|
||||||
await db
|
await db.insert(ships).values(
|
||||||
.insert(ships)
|
shipsData.map((ship) => ({
|
||||||
.values(
|
id: createId(),
|
||||||
shipsData.map((ship) => ({ ...ship, user_game_id: user_Game.id })),
|
user_game_id: user_Game.id,
|
||||||
|
...ship,
|
||||||
|
})),
|
||||||
)
|
)
|
||||||
|
|
||||||
socket
|
socket
|
||||||
|
@ -270,7 +277,7 @@ export async function GET({ request }: APIEvent) {
|
||||||
if (!user_Game?.game) return
|
if (!user_Game?.game) return
|
||||||
await db
|
await db
|
||||||
.insert(moves)
|
.insert(moves)
|
||||||
.values({ ...props, user_game_id: user_Game.id })
|
.values({ ...props, id: createId(), user_game_id: user_Game.id })
|
||||||
.returning()
|
.returning()
|
||||||
|
|
||||||
const game = user_Game.game
|
const game = user_Game.game
|
||||||
|
@ -286,7 +293,7 @@ export async function GET({ request }: APIEvent) {
|
||||||
["debug"],
|
["debug"],
|
||||||
socket.request,
|
socket.request,
|
||||||
)
|
)
|
||||||
if (socket.data.index === undefined || !socket.data.gameId) return
|
if (!socket.data.gameId) return
|
||||||
socket.to(socket.data.gameId).emit("playerEvent", {
|
socket.to(socket.data.gameId).emit("playerEvent", {
|
||||||
type: "disconnect",
|
type: "disconnect",
|
||||||
i: socket.data.index,
|
i: socket.data.index,
|
||||||
|
@ -298,5 +305,5 @@ export async function GET({ request }: APIEvent) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return res
|
return new Response()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
// import { toast } from "react-toastify"
|
|
||||||
// import { createEffect } from "solid-js"
|
|
||||||
// import { useNavigate } from "solid-start"
|
|
||||||
// import { useGameProps } from "~/hooks/useGameProps"
|
|
||||||
// import { useSession } from "~/hooks/useSession"
|
|
||||||
|
|
||||||
export default function Game() {
|
|
||||||
// const { payload } = useGameProps()
|
|
||||||
// const navigate = useNavigate()
|
|
||||||
// const session = useSession()
|
|
||||||
|
|
||||||
// createEffect(() => {
|
|
||||||
// const gameId = payload?.game?.id
|
|
||||||
// const path = gameId ? "/game" : "/start"
|
|
||||||
// toast.promise(navigate(path), {
|
|
||||||
// pending: {
|
|
||||||
// render: "Wird weitergeleitet...",
|
|
||||||
// toastId: "pageLoad",
|
|
||||||
// },
|
|
||||||
// success: {
|
|
||||||
// render: gameId
|
|
||||||
// ? "Spiel gefunden!"
|
|
||||||
// : session?.user.id
|
|
||||||
// ? "Kein laufendes Spiel."
|
|
||||||
// : "Kein laufendes Spiel. Bitte anmelden.",
|
|
||||||
// toastId: "pageLoad",
|
|
||||||
// theme: session?.user.id ? "dark" : undefined,
|
|
||||||
// type: gameId ? "success" : "info",
|
|
||||||
// },
|
|
||||||
// error: {
|
|
||||||
// render: "Es ist ein Fehler aufgetreten 🤯",
|
|
||||||
// type: "error",
|
|
||||||
// toastId: "pageLoad",
|
|
||||||
// theme: "colored",
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="h-full bg-theme">
|
|
||||||
<div class="mx-auto flex h-full max-w-screen-md flex-col items-center justify-evenly" />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -3,7 +3,7 @@ import BurgerMenu from "~/components/BurgerMenu"
|
||||||
import Logo from "~/components/Logo"
|
import Logo from "~/components/Logo"
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const navigate = useNavigate()
|
const navigator = useNavigate()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="h-full bg-theme">
|
<div class="h-full bg-theme">
|
||||||
|
@ -18,7 +18,7 @@ export default function Home() {
|
||||||
class="font-farro rounded-lg border-b-4 border-orange-400 bg-warn px-12 pb-4 pt-5 text-2xl font-bold duration-100 active:border-b-0 active:border-t-4 sm:rounded-xl sm:border-b-[6px] sm:px-14 sm:pb-5 sm:pt-6 sm:text-3xl sm:active:border-t-[6px] md:rounded-2xl md:border-b-8 md:px-20 md:pb-6 md:pt-7 md:text-4xl md:active:border-t-8 xl:px-24 xl:pb-8 xl:pt-10 xl:text-5xl"
|
class="font-farro rounded-lg border-b-4 border-orange-400 bg-warn px-12 pb-4 pt-5 text-2xl font-bold duration-100 active:border-b-0 active:border-t-4 sm:rounded-xl sm:border-b-[6px] sm:px-14 sm:pb-5 sm:pt-6 sm:text-3xl sm:active:border-t-[6px] md:rounded-2xl md:border-b-8 md:px-20 md:pb-6 md:pt-7 md:text-4xl md:active:border-t-8 xl:px-24 xl:pb-8 xl:pt-10 xl:text-5xl"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
navigate("/start")
|
navigator("/start")
|
||||||
}, 200)
|
}, 200)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// import Head from "next/head"
|
// import Head from "next/head"
|
||||||
import classNames from "classnames"
|
import classNames from "classnames"
|
||||||
import { createSignal } from "solid-js"
|
import { Show, createSignal } from "solid-js"
|
||||||
import BurgerMenu from "~/components/BurgerMenu"
|
import BurgerMenu from "~/components/BurgerMenu"
|
||||||
import LobbyFrame from "~/components/Lobby/LobbyFrame"
|
import LobbyFrame from "~/components/Lobby/LobbyFrame"
|
||||||
import Settings from "~/components/Lobby/SettingsFrame/Settings"
|
import Settings from "~/components/Lobby/SettingsFrame/Settings"
|
||||||
|
@ -26,16 +26,16 @@ export default function Lobby() {
|
||||||
<div
|
<div
|
||||||
class={classNames(
|
class={classNames(
|
||||||
"mx-auto flex h-full max-w-screen-2xl flex-col items-center justify-evenly",
|
"mx-auto flex h-full max-w-screen-2xl flex-col items-center justify-evenly",
|
||||||
{ "blur-sm": settings },
|
{ "blur-sm": settings() },
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Logo small={true} />
|
<Logo small={true} />
|
||||||
<LobbyFrame openSettings={() => setSettings(true)} />
|
<LobbyFrame openSettings={() => setSettings(true)} />
|
||||||
</div>
|
</div>
|
||||||
<BurgerMenu blur={settings()} />
|
<BurgerMenu blur={settings()} />
|
||||||
{settings() ? (
|
<Show when={settings()}>
|
||||||
<Settings closeSettings={() => setSettings(false)} />
|
<Settings closeSettings={() => setSettings(false)} />
|
||||||
) : null}
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { signIn } from "@auth/solid-start/client"
|
import { signIn } from "@auth/solid-start/client"
|
||||||
import { faLeftLong } from "@fortawesome/pro-solid-svg-icons"
|
import { faLeftLong } from "@fortawesome/pro-solid-svg-icons"
|
||||||
import classNames from "classnames"
|
import classNames from "classnames"
|
||||||
import { createEffect, createSignal } from "solid-js"
|
import { Show, createEffect, createSignal } from "solid-js"
|
||||||
import { useNavigate, useSearchParams } from "solid-start"
|
import { useNavigate, useSearchParams } from "solid-start"
|
||||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
||||||
import { useSession } from "~/hooks/useSession"
|
import { useSession } from "~/hooks/useSession"
|
||||||
|
@ -39,19 +39,20 @@ const errors: Record<SignInErrorTypes, string> = {
|
||||||
|
|
||||||
function Login() {
|
function Login() {
|
||||||
const [email, setEmail] = createSignal("")
|
const [email, setEmail] = createSignal("")
|
||||||
const { latest } = useSession()
|
const { session } = useSession()
|
||||||
const navigate = useNavigate()
|
const navigator = useNavigate()
|
||||||
const [searchParams] = useSearchParams()
|
const [searchParams] = useSearchParams()
|
||||||
|
|
||||||
const errorType = searchParams["error"] as SignInErrorTypes
|
const errorType = searchParams["error"] as SignInErrorTypes
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!errorType) return
|
if (!errorType) return
|
||||||
|
console.error(errors[errorType] ?? errors.default)
|
||||||
// toast.error(errors[errorType] ?? errors.default, { theme: "colored" })
|
// toast.error(errors[errorType] ?? errors.default, { theme: "colored" })
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (latest?.user?.id) navigate("/")
|
if (session()) navigator("/signout")
|
||||||
})
|
})
|
||||||
|
|
||||||
const login = (provider: "email" | "azure-ad") =>
|
const login = (provider: "email" | "azure-ad") =>
|
||||||
|
@ -79,7 +80,14 @@ function Login() {
|
||||||
</div>
|
</div>
|
||||||
{errorType && <hr class="mb-8 border-gray-400" />}
|
{errorType && <hr class="mb-8 border-gray-400" />}
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex flex-col">
|
<form
|
||||||
|
class="flex flex-col"
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
if (!email()) return
|
||||||
|
login("email")
|
||||||
|
}}
|
||||||
|
>
|
||||||
<label for="email" class="mx-2 text-lg">
|
<label for="email" class="mx-2 text-lg">
|
||||||
Email
|
Email
|
||||||
</label>
|
</label>
|
||||||
|
@ -95,12 +103,11 @@ function Login() {
|
||||||
<button
|
<button
|
||||||
id="email-submit"
|
id="email-submit"
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={() => login("email")}
|
|
||||||
class="my-1 rounded-lg bg-blue-500 bg-opacity-75 px-10 py-3 text-white shadow-inner drop-shadow-md backdrop-blur-md transition-colors duration-300 hover:bg-blue-600"
|
class="my-1 rounded-lg bg-blue-500 bg-opacity-75 px-10 py-3 text-white shadow-inner drop-shadow-md backdrop-blur-md transition-colors duration-300 hover:bg-blue-600"
|
||||||
>
|
>
|
||||||
Sign in with Email
|
Sign in with Email
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</form>
|
||||||
|
|
||||||
<div class="flex flex-row items-center">
|
<div class="flex flex-row items-center">
|
||||||
<hr class="w-full" />
|
<hr class="w-full" />
|
||||||
|
@ -137,21 +144,19 @@ function Login() {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{errorType ? (
|
<Show when={errorType}>
|
||||||
<>
|
|
||||||
<hr class="mt-8 border-gray-400" />
|
<hr class="mt-8 border-gray-400" />
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<button
|
<button
|
||||||
id="back"
|
id="back"
|
||||||
onClick={() => navigate("/")}
|
onClick={() => navigator("/")}
|
||||||
class="mt-10 rounded-lg border-2 border-gray-400 bg-gray-500 bg-opacity-75 px-16 py-2 text-white shadow-inner drop-shadow-md backdrop-blur-md transition-colors duration-300 hover:border-blue-600"
|
class="mt-10 rounded-lg border-2 border-gray-400 bg-gray-500 bg-opacity-75 px-16 py-2 text-white shadow-inner drop-shadow-md backdrop-blur-md transition-colors duration-300 hover:border-blue-600"
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faLeftLong} />
|
<FontAwesomeIcon icon={faLeftLong} />
|
||||||
<span class="mx-4 font-bold">Return</span>
|
<span class="mx-4 font-bold">Return</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</Show>
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import { signOut } from "@auth/solid-start/client"
|
import { signOut } from "@auth/solid-start/client"
|
||||||
|
import { faLeftLong } from "@fortawesome/pro-solid-svg-icons"
|
||||||
import { createEffect } from "solid-js"
|
import { createEffect } from "solid-js"
|
||||||
import { useNavigate } from "solid-start"
|
import { useNavigate } from "solid-start"
|
||||||
|
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
||||||
import { useSession } from "~/hooks/useSession"
|
import { useSession } from "~/hooks/useSession"
|
||||||
|
|
||||||
function Logout() {
|
function Logout() {
|
||||||
const { state } = useSession()
|
const { session } = useSession()
|
||||||
|
|
||||||
const navigator = useNavigate()
|
const navigator = useNavigate()
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (state === "ready") navigator("/signin") // TODO
|
if (!session()) navigator("/signin")
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -36,6 +37,17 @@ function Logout() {
|
||||||
Sign out
|
Sign out
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<hr class="mt-8 border-gray-400" />
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<button
|
||||||
|
id="back"
|
||||||
|
onClick={() => navigator("/")}
|
||||||
|
class="mt-10 rounded-lg border-2 border-gray-400 bg-gray-500 bg-opacity-75 px-16 py-2 text-white shadow-inner drop-shadow-md backdrop-blur-md transition-colors duration-300 hover:border-blue-600"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faLeftLong} />
|
||||||
|
<span class="mx-4 font-bold">Return</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,7 +10,7 @@ import BurgerMenu from "~/components/BurgerMenu"
|
||||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
||||||
import Logo from "~/components/Logo"
|
import Logo from "~/components/Logo"
|
||||||
import OptionButton from "~/components/OptionButton"
|
import OptionButton from "~/components/OptionButton"
|
||||||
import { useGameProps } from "~/hooks/useGameProps"
|
import { full } from "~/hooks/useGameProps"
|
||||||
import { useSession } from "~/hooks/useSession"
|
import { useSession } from "~/hooks/useSession"
|
||||||
|
|
||||||
export function isAuthenticated(res: Response) {
|
export function isAuthenticated(res: Response) {
|
||||||
|
@ -44,11 +44,11 @@ export function isAuthenticated(res: Response) {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
export default function Start() {
|
export default function Start() {
|
||||||
const [otp] = createSignal("")
|
const [otp, setOtp] = createSignal("")
|
||||||
const gameProps = useGameProps()
|
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const navigate = useNavigate()
|
const navigator = useNavigate()
|
||||||
const session = useSession()
|
const { session } = useSession()
|
||||||
|
|
||||||
const [searchParams] = useSearchParams()
|
const [searchParams] = useSearchParams()
|
||||||
|
|
||||||
const query = () => {
|
const query = () => {
|
||||||
|
@ -82,7 +82,8 @@ export default function Start() {
|
||||||
// hideProgressBar: true,
|
// hideProgressBar: true,
|
||||||
// closeButton: false,
|
// closeButton: false,
|
||||||
// })
|
// })
|
||||||
const res = await gameRequestPromise.catch(() => {
|
const res = await gameRequestPromise.catch((err) => {
|
||||||
|
console.log(err)
|
||||||
// toast.update(toastId, {
|
// toast.update(toastId, {
|
||||||
// render: "Es ist ein Fehler aufgetreten bei der Anfrage 🤯",
|
// render: "Es ist ein Fehler aufgetreten bei der Anfrage 🤯",
|
||||||
// type: "error",
|
// type: "error",
|
||||||
|
@ -94,13 +95,13 @@ export default function Start() {
|
||||||
// })
|
// })
|
||||||
})
|
})
|
||||||
if (!res) return
|
if (!res) return
|
||||||
gameProps.full(res)
|
full(res)
|
||||||
|
|
||||||
// toast.update(toastId, {
|
// toast.update(toastId, {
|
||||||
// render: "Weiterleitung",
|
// render: "Weiterleitung",
|
||||||
// })
|
// })
|
||||||
|
|
||||||
navigate("/lobby")
|
navigator("/lobby")
|
||||||
// .then(() =>
|
// .then(() =>
|
||||||
// toast.update(toastId, {
|
// toast.update(toastId, {
|
||||||
// render: "Raum begetreten 👌",
|
// render: "Raum begetreten 👌",
|
||||||
|
@ -141,19 +142,19 @@ export default function Start() {
|
||||||
class="-mt-2 h-14 w-20 self-start rounded-xl border-b-4 border-shield-gray bg-voidDark text-2xl text-grayish duration-100 active:border-b-0 active:border-t-4 sm:-mt-6 sm:w-40 sm:px-2 sm:text-5xl"
|
class="-mt-2 h-14 w-20 self-start rounded-xl border-b-4 border-shield-gray bg-voidDark text-2xl text-grayish duration-100 active:border-b-0 active:border-t-4 sm:-mt-6 sm:w-40 sm:px-2 sm:text-5xl"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
navigate("/")
|
navigator("/")
|
||||||
}, 200)
|
}, 200)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faLeftLong} />
|
<FontAwesomeIcon icon={faLeftLong} />
|
||||||
</button>
|
</button>
|
||||||
{!session.latest?.user?.id && (
|
{!session()?.user?.id && (
|
||||||
<button
|
<button
|
||||||
id="login"
|
id="login"
|
||||||
class="-mt-2 h-14 w-20 self-start rounded-xl border-b-4 border-orange-500 bg-yellow-500 text-2xl active:border-b-0 active:border-t-4 sm:-mt-6 sm:w-40 sm:px-2 sm:text-4xl"
|
class="-mt-2 h-14 w-20 self-start rounded-xl border-b-4 border-orange-500 bg-yellow-500 text-2xl active:border-b-0 active:border-t-4 sm:-mt-6 sm:w-40 sm:px-2 sm:text-4xl"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
navigate("/signin")
|
navigator("/signin")
|
||||||
}, 200)
|
}, 200)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -166,12 +167,12 @@ export default function Start() {
|
||||||
text="Raum erstellen"
|
text="Raum erstellen"
|
||||||
callback={gameFetch}
|
callback={gameFetch}
|
||||||
icon={faPlus}
|
icon={faPlus}
|
||||||
disabled={!session.latest}
|
disabled={!session()}
|
||||||
/>
|
/>
|
||||||
<OptionButton
|
<OptionButton
|
||||||
text="Raum beitreten"
|
text="Raum beitreten"
|
||||||
callback={() =>
|
callback={() =>
|
||||||
navigate(
|
navigator(
|
||||||
location.pathname.concat(
|
location.pathname.concat(
|
||||||
"?",
|
"?",
|
||||||
new URLSearchParams({ q: "join" }).toString(),
|
new URLSearchParams({ q: "join" }).toString(),
|
||||||
|
@ -179,11 +180,11 @@ export default function Start() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
icon={faUserPlus}
|
icon={faUserPlus}
|
||||||
disabled={!session.latest}
|
disabled={!session()}
|
||||||
nodeWhen={query().join && !!session.latest}
|
nodeWhen={query().join && !!session()}
|
||||||
node={
|
node={
|
||||||
<>
|
<input value={otp()} onInput={(e) => setOtp(e.target.value)} />
|
||||||
{
|
|
||||||
// <OtpInput
|
// <OtpInput
|
||||||
// shouldAutoFocus
|
// shouldAutoFocus
|
||||||
// containerStyle={{ color: "initial" }}
|
// containerStyle={{ color: "initial" }}
|
||||||
|
@ -197,14 +198,12 @@ export default function Start() {
|
||||||
// renderInput={(props) => <input {...props} />}
|
// renderInput={(props) => <input {...props} />}
|
||||||
// />
|
// />
|
||||||
}
|
}
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<OptionButton
|
<OptionButton
|
||||||
text="Zuschauen"
|
text="Zuschauen"
|
||||||
icon={faEye}
|
icon={faEye}
|
||||||
callback={() =>
|
callback={() =>
|
||||||
navigate(
|
navigator(
|
||||||
location.pathname +
|
location.pathname +
|
||||||
"?" +
|
"?" +
|
||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue