Working game logic
This commit is contained in:
parent
e0e0f0a728
commit
0a6fd88733
27 changed files with 1458 additions and 718 deletions
|
@ -1,5 +1,6 @@
|
|||
import { count } from "./Gamefield"
|
||||
import { useGameProps } from "@hooks/useGameProps"
|
||||
import useIndex from "@hooks/useIndex"
|
||||
import useShips from "@hooks/useShips"
|
||||
import {
|
||||
borderCN,
|
||||
|
@ -22,12 +23,10 @@ type TilesType = {
|
|||
}
|
||||
|
||||
function BorderTiles() {
|
||||
const { activeUser } = useIndex()
|
||||
const {
|
||||
DispatchAction,
|
||||
payload,
|
||||
mode,
|
||||
hits,
|
||||
target,
|
||||
targetPreview,
|
||||
mouseCursor,
|
||||
setTarget,
|
||||
|
@ -41,17 +40,18 @@ function BorderTiles() {
|
|||
const list = targetList(targetPreview, mode)
|
||||
if (
|
||||
!isGameTile ||
|
||||
!list.filter(({ x, y }) => !isAlreadyHit(x, y, hits)).length
|
||||
!list.filter(
|
||||
({ x, y }) => !isAlreadyHit(x, y, activeUser?.hits ?? [])
|
||||
).length
|
||||
)
|
||||
return
|
||||
if (target.show && target.x == x && target.y == y) {
|
||||
DispatchAction({
|
||||
action: "missile",
|
||||
...target,
|
||||
if (!overlapsWithAnyBorder(targetPreview, mode))
|
||||
setTarget({
|
||||
show: true,
|
||||
x,
|
||||
y,
|
||||
orientation: targetPreview.orientation,
|
||||
})
|
||||
setTarget((t) => ({ ...t, show: false }))
|
||||
} else if (!overlapsWithAnyBorder(targetPreview, mode))
|
||||
setTarget({ show: true, x, y })
|
||||
} else if (
|
||||
payload?.game?.state === "starting" &&
|
||||
targetPreview.show &&
|
||||
|
@ -62,15 +62,13 @@ function BorderTiles() {
|
|||
}
|
||||
},
|
||||
[
|
||||
DispatchAction,
|
||||
hits,
|
||||
activeUser?.hits,
|
||||
mode,
|
||||
payload?.game?.state,
|
||||
setMouseCursor,
|
||||
setShips,
|
||||
setTarget,
|
||||
ships,
|
||||
target,
|
||||
targetPreview,
|
||||
]
|
||||
)
|
||||
|
|
|
@ -7,13 +7,14 @@ import {
|
|||
faSquare4,
|
||||
} from "@fortawesome/pro-regular-svg-icons"
|
||||
import {
|
||||
faArrowRightFromBracket,
|
||||
faBroomWide,
|
||||
faCheck,
|
||||
faComments,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faFlag,
|
||||
faGlasses,
|
||||
faLock,
|
||||
faPalette,
|
||||
faReply,
|
||||
faRotate,
|
||||
|
@ -21,14 +22,18 @@ import {
|
|||
faShip,
|
||||
faSparkles,
|
||||
faSwords,
|
||||
faXmark,
|
||||
} from "@fortawesome/pro-solid-svg-icons"
|
||||
import { useDrawProps } from "@hooks/useDrawProps"
|
||||
import { useGameProps } from "@hooks/useGameProps"
|
||||
import useIndex from "@hooks/useIndex"
|
||||
import useShips from "@hooks/useShips"
|
||||
import { socket } from "@lib/socket"
|
||||
import { modes } from "@lib/utils/helpers"
|
||||
import { GamePropsSchema } from "@lib/zodSchemas"
|
||||
import { useSession } from "next-auth/react"
|
||||
import { useRouter } from "next/router"
|
||||
import { useCallback, useEffect, useMemo } from "react"
|
||||
import { Icons, toast } from "react-toastify"
|
||||
|
||||
export function setGameSetting(
|
||||
payload: GameSettings,
|
||||
|
@ -47,26 +52,27 @@ export function setGameSetting(
|
|||
|
||||
function EventBar({ clear }: { clear: () => void }) {
|
||||
const { shouldHide, color } = useDrawProps()
|
||||
const { data: session } = useSession()
|
||||
const { selfIndex, isActiveIndex, selfUser } = useIndex()
|
||||
const { ships } = useShips()
|
||||
const router = useRouter()
|
||||
const {
|
||||
payload,
|
||||
userStates,
|
||||
menu,
|
||||
mode,
|
||||
setSetting,
|
||||
full,
|
||||
target,
|
||||
setTarget,
|
||||
setTargetPreview,
|
||||
setIsReady,
|
||||
reset,
|
||||
} = useGameProps()
|
||||
const { ships } = useShips()
|
||||
const gameSetting = useCallback(
|
||||
(payload: GameSettings) => setGameSetting(payload, setSetting, full),
|
||||
[full, setSetting]
|
||||
)
|
||||
const self = useMemo(
|
||||
() => payload?.users.find((e) => e?.id === session?.user.id),
|
||||
[payload?.users, session?.user.id]
|
||||
)
|
||||
|
||||
const items = useMemo<EventBarModes>(
|
||||
() => ({
|
||||
main: [
|
||||
|
@ -82,14 +88,14 @@ function EventBar({ clear }: { clear: () => void }) {
|
|||
icon: faSwords,
|
||||
text: "Attack",
|
||||
callback: () => {
|
||||
useGameProps.setState({ menu: "actions" })
|
||||
useGameProps.setState({ menu: "moves" })
|
||||
},
|
||||
}
|
||||
: {
|
||||
icon: faShip,
|
||||
text: "Ships",
|
||||
callback: () => {
|
||||
useGameProps.setState({ menu: "actions" })
|
||||
useGameProps.setState({ menu: "moves" })
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -109,20 +115,21 @@ function EventBar({ clear }: { clear: () => void }) {
|
|||
],
|
||||
menu: [
|
||||
{
|
||||
icon: faArrowRightFromBracket,
|
||||
text: "Leave",
|
||||
icon: faFlag,
|
||||
text: "Surrender",
|
||||
iconColor: "darkred",
|
||||
callback: () => {
|
||||
// router.push()
|
||||
useGameProps.setState({ menu: "surrender" })
|
||||
},
|
||||
},
|
||||
],
|
||||
actions:
|
||||
moves:
|
||||
payload?.game?.state === "running"
|
||||
? [
|
||||
{
|
||||
icon: "scope",
|
||||
text: "Fire missile",
|
||||
enabled: mode === 0,
|
||||
callback: () => {
|
||||
useGameProps.setState({ mode: 0 })
|
||||
setTarget((e) => ({ ...e, show: false }))
|
||||
|
@ -131,10 +138,11 @@ function EventBar({ clear }: { clear: () => void }) {
|
|||
{
|
||||
icon: "torpedo",
|
||||
text: "Fire torpedo",
|
||||
enabled: mode === 1 || mode === 2,
|
||||
amount:
|
||||
2 -
|
||||
(self?.moves.filter(
|
||||
(e) => e.action === "htorpedo" || e.action === "vtorpedo"
|
||||
((selfUser?.moves ?? []).filter(
|
||||
(e) => e.type === "htorpedo" || e.type === "vtorpedo"
|
||||
).length ?? 0),
|
||||
callback: () => {
|
||||
useGameProps.setState({ mode: 1 })
|
||||
|
@ -144,9 +152,11 @@ function EventBar({ clear }: { clear: () => void }) {
|
|||
{
|
||||
icon: "radar",
|
||||
text: "Radar scan",
|
||||
enabled: mode === 3,
|
||||
amount:
|
||||
1 -
|
||||
(self?.moves.filter((e) => e.action === "radar").length ?? 0),
|
||||
((selfUser?.moves ?? []).filter((e) => e.type === "radar")
|
||||
.length ?? 0),
|
||||
callback: () => {
|
||||
useGameProps.setState({ mode: 3 })
|
||||
setTarget((e) => ({ ...e, show: false }))
|
||||
|
@ -191,19 +201,6 @@ function EventBar({ clear }: { clear: () => void }) {
|
|||
}))
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: faCheck,
|
||||
text: "Done",
|
||||
disabled: mode >= 0,
|
||||
callback: () => {
|
||||
if (!payload || !session?.user.id) return
|
||||
const i = payload.users.findIndex(
|
||||
(user) => session.user.id === user?.id
|
||||
)
|
||||
setIsReady({ isReady: true, i })
|
||||
socket.emit("isReady", true)
|
||||
},
|
||||
},
|
||||
],
|
||||
draw: [
|
||||
{ icon: faBroomWide, text: "Clear", callback: clear },
|
||||
|
@ -248,39 +245,89 @@ function EventBar({ clear }: { clear: () => void }) {
|
|||
}),
|
||||
},
|
||||
],
|
||||
surrender: [
|
||||
{
|
||||
icon: faCheck,
|
||||
text: "Yes",
|
||||
iconColor: "green",
|
||||
callback: async () => {
|
||||
socket.emit("gameState", "aborted")
|
||||
await router.push("/")
|
||||
reset()
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: faXmark,
|
||||
text: "No",
|
||||
iconColor: "red",
|
||||
callback: () => {
|
||||
useGameProps.setState({ menu: "main" })
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
[
|
||||
payload?.game?.state,
|
||||
payload?.game?.allowSpectators,
|
||||
payload?.game?.allowSpecials,
|
||||
payload?.game?.allowChat,
|
||||
payload?.game?.allowMarkDraw,
|
||||
mode,
|
||||
selfUser?.moves,
|
||||
ships,
|
||||
clear,
|
||||
color,
|
||||
shouldHide,
|
||||
gameSetting,
|
||||
mode,
|
||||
payload,
|
||||
self?.moves,
|
||||
session?.user.id,
|
||||
setIsReady,
|
||||
setTarget,
|
||||
setTargetPreview,
|
||||
ships,
|
||||
shouldHide,
|
||||
router,
|
||||
reset,
|
||||
]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
menu !== "actions" ||
|
||||
menu !== "moves" ||
|
||||
payload?.game?.state !== "starting" ||
|
||||
mode < 0 ||
|
||||
items.actions[mode].amount
|
||||
items.moves[mode].amount
|
||||
)
|
||||
return
|
||||
const index = items.actions.findIndex((e) => e.amount)
|
||||
const index = items.moves.findIndex((e) => e.amount)
|
||||
useGameProps.setState({ mode: index })
|
||||
}, [items.actions, menu, mode, payload?.game?.state])
|
||||
}, [items.moves, menu, mode, payload?.game?.state])
|
||||
|
||||
useEffect(() => {
|
||||
useDrawProps.setState({ enable: menu === "draw" })
|
||||
}, [menu])
|
||||
|
||||
useEffect(() => {
|
||||
if (payload?.game?.state !== "running") return
|
||||
|
||||
let toastId = "otherPlayer"
|
||||
if (isActiveIndex) toast.dismiss(toastId)
|
||||
else
|
||||
toast.info("Waiting for other player...", {
|
||||
toastId,
|
||||
position: "top-right",
|
||||
icon: Icons.spinner(),
|
||||
autoClose: false,
|
||||
hideProgressBar: true,
|
||||
closeButton: false,
|
||||
})
|
||||
|
||||
// toastId = "connect_error"
|
||||
// const isActive = toast.isActive(toastId)
|
||||
// console.log(toastId, isActive)
|
||||
// if (isActive)
|
||||
// toast.update(toastId, {
|
||||
// autoClose: 5000,
|
||||
// })
|
||||
// else
|
||||
// toast.warn("Spie", { toastId })
|
||||
}, [isActiveIndex, menu, payload?.game?.state])
|
||||
|
||||
return (
|
||||
<div className="event-bar">
|
||||
{menu !== "main" && (
|
||||
|
@ -295,9 +342,50 @@ function EventBar({ clear }: { clear: () => void }) {
|
|||
}}
|
||||
></Item>
|
||||
)}
|
||||
{items[menu].map((e, i) => (
|
||||
<Item key={i} props={e} />
|
||||
))}
|
||||
{items[menu].map((e, i) => {
|
||||
if (!isActiveIndex && menu === "main" && i === 1) return
|
||||
return <Item key={i} props={e} />
|
||||
})}
|
||||
{menu === "moves" && (
|
||||
<Item
|
||||
props={{
|
||||
icon:
|
||||
selfIndex >= 0 && userStates[selfIndex].isReady
|
||||
? faLock
|
||||
: faCheck,
|
||||
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: () => {
|
||||
if (selfIndex < 0) return
|
||||
if (payload?.game?.state === "starting") {
|
||||
const isReady = !userStates[selfIndex].isReady
|
||||
setIsReady({ isReady, i: selfIndex })
|
||||
socket.emit("isReady", isReady)
|
||||
}
|
||||
if (payload?.game?.state === "running") {
|
||||
const i = (selfUser?.moves ?? [])
|
||||
.map((e) => e.index)
|
||||
.reduce((prev, curr) => (curr > prev ? curr : prev), 0)
|
||||
const props = {
|
||||
type: modes[mode].type,
|
||||
x: target.x,
|
||||
y: target.y,
|
||||
orientation: target.orientation,
|
||||
index: (selfUser?.moves ?? []).length ? i + 1 : 0,
|
||||
}
|
||||
socket.emit("dispatchMove", props)
|
||||
setTarget((t) => ({ ...t, show: false }))
|
||||
}
|
||||
},
|
||||
}}
|
||||
></Item>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -9,15 +9,20 @@ import Targets from "@components/Gamefield/Targets"
|
|||
import { useDraw } from "@hooks/useDraw"
|
||||
import { useDrawProps } from "@hooks/useDrawProps"
|
||||
import { useGameProps } from "@hooks/useGameProps"
|
||||
import useIndex from "@hooks/useIndex"
|
||||
import useSocket from "@hooks/useSocket"
|
||||
import { socket } from "@lib/socket"
|
||||
import { overlapsWithAnyBorder } from "@lib/utils/helpers"
|
||||
import { useRouter } from "next/router"
|
||||
import { CSSProperties } from "react"
|
||||
import { useEffect } from "react"
|
||||
import { toast } from "react-toastify"
|
||||
|
||||
export const count = 12
|
||||
|
||||
function Gamefield() {
|
||||
const { isActiveIndex, selfUser } = useIndex()
|
||||
const router = useRouter()
|
||||
const {
|
||||
userStates,
|
||||
mode,
|
||||
|
@ -27,17 +32,22 @@ function Gamefield() {
|
|||
payload,
|
||||
setTargetPreview,
|
||||
full,
|
||||
reset,
|
||||
} = useGameProps()
|
||||
const { isConnected } = useSocket()
|
||||
|
||||
const { canvasRef, onMouseDown, clear } = useDraw()
|
||||
const { enable, color, shouldHide } = useDrawProps()
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
payload?.game?.state !== "starting" ||
|
||||
userStates.reduce((prev, curr) => prev || !curr.isReady, false)
|
||||
)
|
||||
return
|
||||
socket.emit("ships", selfUser?.ships ?? [])
|
||||
socket.emit("gameState", "running")
|
||||
}, [payload?.game?.state, userStates])
|
||||
}, [payload?.game?.state, selfUser?.ships, userStates])
|
||||
|
||||
useEffect(() => {
|
||||
if (payload?.game?.id || !isConnected) return
|
||||
|
@ -78,8 +88,18 @@ function Gamefield() {
|
|||
}
|
||||
}, [mode, mouseCursor, payload?.game?.state, setTargetPreview, target])
|
||||
|
||||
const { canvasRef, onMouseDown, clear } = useDraw()
|
||||
const { enable, color, shouldHide } = useDrawProps()
|
||||
useEffect(() => {
|
||||
if (payload?.game?.state !== "aborted") return
|
||||
toast.info("Enemy gave up!")
|
||||
router.push("/")
|
||||
reset()
|
||||
}, [payload?.game?.state, reset, router])
|
||||
|
||||
useEffect(() => {
|
||||
if (payload?.game?.id) return
|
||||
const timeout = setTimeout(() => router.push("/"), 5000)
|
||||
return () => clearTimeout(timeout)
|
||||
}, [payload?.game?.id, router])
|
||||
|
||||
return (
|
||||
<div id="gamefield">
|
||||
|
@ -98,8 +118,8 @@ function Gamefield() {
|
|||
<Labeling />
|
||||
|
||||
{/* Ships */}
|
||||
<Ships />
|
||||
<HitElems />
|
||||
{(payload?.game?.state !== "running" || !isActiveIndex) && <Ships />}
|
||||
|
||||
{/* Fog images */}
|
||||
{/* <FogImages /> */}
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
import { Target, TargetList } from "../../interfaces/frontend"
|
||||
import { PointerProps } from "../../interfaces/frontend"
|
||||
import { faCrosshairs } from "@fortawesome/pro-solid-svg-icons"
|
||||
import { faRadar } from "@fortawesome/pro-thin-svg-icons"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import classNames from "classnames"
|
||||
import { CSSProperties } from "react"
|
||||
|
||||
export interface PointerProps extends Target, TargetList {
|
||||
imply: boolean
|
||||
}
|
||||
|
||||
function GamefieldPointer({
|
||||
props: { x, y, show, type, edges, imply },
|
||||
preview,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Hit } from "../../interfaces/frontend"
|
||||
import { faBurst, faXmark } from "@fortawesome/pro-solid-svg-icons"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { useGameProps } from "@hooks/useGameProps"
|
||||
import useIndex from "@hooks/useIndex"
|
||||
import { CSSProperties } from "react"
|
||||
|
||||
function HitElems({
|
||||
|
@ -9,11 +9,11 @@ function HitElems({
|
|||
}: {
|
||||
props?: { hits: Hit[]; colorOverride?: string }
|
||||
}) {
|
||||
const { hits } = useGameProps()
|
||||
const { activeUser } = useIndex()
|
||||
|
||||
return (
|
||||
<>
|
||||
{(props?.hits ?? hits).map(({ hit, x, y }, i) => (
|
||||
{(props?.hits ?? activeUser?.hits ?? []).map(({ hit, x, y }, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="hit-svg"
|
||||
|
|
|
@ -2,11 +2,12 @@ import { ItemProps } from "../../interfaces/frontend"
|
|||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { useDrawProps } from "@hooks/useDrawProps"
|
||||
import classNames from "classnames"
|
||||
import { enable } from "colors"
|
||||
import React, { CSSProperties, useEffect, useRef, useState } from "react"
|
||||
import { HexColorPicker } from "react-colorful"
|
||||
|
||||
function Item({
|
||||
props: { icon, text, amount, iconColor, disabled, callback },
|
||||
props: { icon, text, amount, iconColor, disabled, enabled, callback },
|
||||
}: {
|
||||
props: ItemProps
|
||||
}) {
|
||||
|
@ -47,7 +48,7 @@ function Item({
|
|||
className={classNames("container", {
|
||||
amount: typeof amount !== "undefined",
|
||||
disabled: disabled || amount === 0,
|
||||
enabled: disabled === false,
|
||||
enabled: disabled === false || enabled,
|
||||
})}
|
||||
style={
|
||||
typeof amount !== "undefined"
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import Ship from "./Ship"
|
||||
import useShips from "@hooks/useShips"
|
||||
import useIndex from "@hooks/useIndex"
|
||||
|
||||
function Ships() {
|
||||
const { ships } = useShips()
|
||||
const { selfUser } = useIndex()
|
||||
|
||||
return (
|
||||
<>
|
||||
{ships.map((props, i) => (
|
||||
{selfUser?.ships.map((props, i) => (
|
||||
<Ship key={i} props={props} />
|
||||
))}
|
||||
</>
|
||||
|
|
|
@ -2,6 +2,7 @@ import GamefieldPointer from "./GamefieldPointer"
|
|||
import HitElems from "./HitElems"
|
||||
import Ship from "./Ship"
|
||||
import { useGameProps } from "@hooks/useGameProps"
|
||||
import useIndex from "@hooks/useIndex"
|
||||
import useShips from "@hooks/useShips"
|
||||
import {
|
||||
composeTargetTiles,
|
||||
|
@ -10,17 +11,22 @@ import {
|
|||
} from "@lib/utils/helpers"
|
||||
|
||||
function Targets() {
|
||||
const { payload, target, targetPreview, mode, hits } = useGameProps()
|
||||
const { activeUser } = useIndex()
|
||||
const { payload, target, targetPreview, mode } = useGameProps()
|
||||
const { ships } = useShips()
|
||||
|
||||
if (payload?.game?.state === "running")
|
||||
return (
|
||||
<>
|
||||
{[
|
||||
...composeTargetTiles(target, mode, hits).map((props, i) => (
|
||||
<GamefieldPointer key={"t" + i} props={props} />
|
||||
)),
|
||||
...composeTargetTiles(targetPreview, mode, hits).map((props, i) => (
|
||||
...composeTargetTiles(target, mode, activeUser?.hits ?? []).map(
|
||||
(props, i) => <GamefieldPointer key={"t" + i} props={props} />
|
||||
),
|
||||
...composeTargetTiles(
|
||||
targetPreview,
|
||||
mode,
|
||||
activeUser?.hits ?? []
|
||||
).map((props, i) => (
|
||||
<GamefieldPointer key={"p" + i} props={props} preview />
|
||||
)),
|
||||
]}
|
||||
|
|
|
@ -48,7 +48,7 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) {
|
|||
|
||||
useEffect(() => {
|
||||
if (!launching || launchTime > 0) return
|
||||
socket.emit("gameState", "running")
|
||||
socket.emit("gameState", "starting")
|
||||
}, [launching, launchTime, router])
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -73,7 +73,7 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) {
|
|||
payload?.game?.state === "lobby"
|
||||
)
|
||||
return
|
||||
router.push("gamefield")
|
||||
router.push("/gamefield")
|
||||
})
|
||||
|
||||
return (
|
||||
|
|
|
@ -7,12 +7,12 @@ import { ReactNode } from "react"
|
|||
|
||||
function OptionButton({
|
||||
icon,
|
||||
action,
|
||||
callback,
|
||||
children,
|
||||
disabled,
|
||||
}: {
|
||||
icon: FontAwesomeIconProps["icon"]
|
||||
action?: () => void
|
||||
callback?: () => void
|
||||
children: ReactNode
|
||||
disabled?: boolean
|
||||
}) {
|
||||
|
@ -24,7 +24,7 @@ function OptionButton({
|
|||
? "border-b-4 border-shield-gray bg-voidDark active:border-b-0 active:border-t-4"
|
||||
: "border-4 border-dashed border-slate-600 bg-red-950"
|
||||
)}
|
||||
onClick={() => action && setTimeout(action, 200)}
|
||||
onClick={() => callback && setTimeout(callback, 200)}
|
||||
disabled={disabled}
|
||||
title={!disabled ? "" : "Please login"}
|
||||
>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import {
|
||||
ActionDispatchProps,
|
||||
MoveDispatchProps,
|
||||
EventBarModes,
|
||||
Hit,
|
||||
MouseCursor,
|
||||
ShipProps,
|
||||
Target,
|
||||
|
@ -14,13 +13,15 @@ import {
|
|||
initlialMouseCursor,
|
||||
initlialTarget,
|
||||
initlialTargetPreview,
|
||||
intersectingShip,
|
||||
targetList,
|
||||
} from "@lib/utils/helpers"
|
||||
import {
|
||||
GamePropsSchema,
|
||||
optionalGamePropsSchema,
|
||||
PlayerSchema,
|
||||
} from "@lib/zodSchemas"
|
||||
import { GameState } from "@prisma/client"
|
||||
import { GameState, MoveType } from "@prisma/client"
|
||||
import { produce } from "immer"
|
||||
import { SetStateAction } from "react"
|
||||
import { toast } from "react-toastify"
|
||||
|
@ -34,16 +35,14 @@ const initialState: optionalGamePropsSchema & {
|
|||
}[]
|
||||
menu: keyof EventBarModes
|
||||
mode: number
|
||||
hits: Hit[]
|
||||
target: Target
|
||||
targetPreview: TargetPreview
|
||||
mouseCursor: MouseCursor
|
||||
} = {
|
||||
menu: "actions",
|
||||
menu: "moves",
|
||||
mode: 0,
|
||||
payload: null,
|
||||
hash: null,
|
||||
hits: [],
|
||||
target: initlialTarget,
|
||||
targetPreview: initlialTargetPreview,
|
||||
mouseCursor: initlialMouseCursor,
|
||||
|
@ -56,7 +55,7 @@ const initialState: optionalGamePropsSchema & {
|
|||
export type State = typeof initialState
|
||||
|
||||
export type Action = {
|
||||
DispatchAction: (props: ActionDispatchProps) => void
|
||||
DispatchMove: (props: MoveDispatchProps, i: number) => void
|
||||
setTarget: (target: SetStateAction<Target>) => void
|
||||
setTargetPreview: (targetPreview: SetStateAction<TargetPreview>) => void
|
||||
setMouseCursor: (mouseCursor: SetStateAction<MouseCursor>) => void
|
||||
|
@ -66,26 +65,54 @@ export type Action = {
|
|||
leave: (cb: () => void) => void
|
||||
setIsReady: (payload: { i: number; isReady: boolean }) => void
|
||||
gameState: (newState: GameState) => void
|
||||
setShips: (ships: ShipProps[], userId: string) => void
|
||||
removeShip: (props: ShipProps, userId: string) => 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>()(
|
||||
devtools(
|
||||
(set) => ({
|
||||
...initialState,
|
||||
DispatchAction: (action) =>
|
||||
setActiveIndex: (i, selfIndex) =>
|
||||
set(
|
||||
produce((state: State) => {
|
||||
// switch (action.type) {
|
||||
// case "fireMissile":
|
||||
// case "htorpedo":
|
||||
// case "vtorpedo": {
|
||||
// state.hits.push(...action.payload)
|
||||
// }
|
||||
// }
|
||||
if (!state.payload) return
|
||||
state.payload.activeIndex = i
|
||||
if (i === selfIndex) {
|
||||
state.menu = "moves"
|
||||
state.mode = 0
|
||||
} else {
|
||||
state.menu = "main"
|
||||
state.mode = -1
|
||||
}
|
||||
})
|
||||
),
|
||||
DispatchMove: (move, i) =>
|
||||
set(
|
||||
produce((state: State) => {
|
||||
if (!state.payload) return
|
||||
const list = targetList(move, move.type)
|
||||
state.payload.users.map((e) => {
|
||||
if (!e) return e
|
||||
if (i === e.index) e.moves.push(move)
|
||||
else if (move.type !== MoveType.radar)
|
||||
e.hits.push(
|
||||
...list.map(({ x, y }) => ({
|
||||
hit: !!intersectingShip(e.ships, {
|
||||
...move,
|
||||
size: 1,
|
||||
variant: 0,
|
||||
}).fields.length,
|
||||
x,
|
||||
y,
|
||||
}))
|
||||
)
|
||||
|
||||
return e
|
||||
})
|
||||
})
|
||||
),
|
||||
setTarget: (dispatch) =>
|
||||
|
@ -112,22 +139,22 @@ export const useGameProps = create<State & Action>()(
|
|||
else state.mouseCursor = dispatch
|
||||
})
|
||||
),
|
||||
setShips: (ships, userId) =>
|
||||
setShips: (ships, index) =>
|
||||
set(
|
||||
produce((state: State) => {
|
||||
if (!state.payload) return
|
||||
state.payload.users = state.payload.users.map((e) => {
|
||||
if (!e || e.id !== userId) return e
|
||||
if (!e || e.index !== index) return e
|
||||
e.ships = ships
|
||||
return e
|
||||
})
|
||||
})
|
||||
),
|
||||
removeShip: ({ size, variant, x, y }, userId) =>
|
||||
removeShip: ({ size, variant, x, y }, index) =>
|
||||
set(
|
||||
produce((state: State) => {
|
||||
state.payload?.users.map((e) => {
|
||||
if (!e || e.id !== userId) return
|
||||
if (!e || e.index !== index) return
|
||||
const indexToRemove = e.ships.findIndex(
|
||||
(ship) =>
|
||||
ship.size === size &&
|
||||
|
|
24
leaky-ships/hooks/useIndex.ts
Normal file
24
leaky-ships/hooks/useIndex.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { useGameProps } from "./useGameProps"
|
||||
import { useSession } from "next-auth/react"
|
||||
|
||||
function useIndex() {
|
||||
const { payload } = useGameProps()
|
||||
const { data: session } = useSession()
|
||||
|
||||
const selfIndex =
|
||||
payload?.users.findIndex((e) => e?.id === session?.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
|
27
leaky-ships/hooks/useShips.ts
Normal file
27
leaky-ships/hooks/useShips.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { ShipProps } from "../interfaces/frontend"
|
||||
import { useGameProps } from "./useGameProps"
|
||||
import useIndex from "./useIndex"
|
||||
import { useCallback, useMemo } from "react"
|
||||
|
||||
function useShips() {
|
||||
const gameProps = useGameProps()
|
||||
const { selfIndex } = useIndex()
|
||||
|
||||
const ships = useMemo(
|
||||
() =>
|
||||
gameProps.payload?.users.find((e) => e?.index === selfIndex)?.ships ?? [],
|
||||
[gameProps.payload?.users, selfIndex]
|
||||
)
|
||||
const setShips = useCallback(
|
||||
(ships: ShipProps[]) => gameProps.setShips(ships, selfIndex),
|
||||
[gameProps, selfIndex]
|
||||
)
|
||||
const removeShip = useCallback(
|
||||
(ship: ShipProps) => gameProps.removeShip(ship, selfIndex),
|
||||
[gameProps, selfIndex]
|
||||
)
|
||||
|
||||
return { ships, setShips, removeShip }
|
||||
}
|
||||
|
||||
export default useShips
|
|
@ -1,28 +0,0 @@
|
|||
import { ShipProps } from "../interfaces/frontend"
|
||||
import { useGameProps } from "./useGameProps"
|
||||
import { useSession } from "next-auth/react"
|
||||
import { useCallback, useMemo } from "react"
|
||||
|
||||
function useShips() {
|
||||
const gameProps = useGameProps()
|
||||
const { data: session } = useSession()
|
||||
|
||||
const ships = useMemo(
|
||||
() =>
|
||||
gameProps.payload?.users.find((e) => e?.id === session?.user.id)?.ships ??
|
||||
[],
|
||||
[gameProps.payload?.users, session?.user.id]
|
||||
)
|
||||
const setShips = useCallback(
|
||||
(ships: ShipProps[]) => gameProps.setShips(ships, session?.user.id ?? ""),
|
||||
[gameProps, session?.user.id]
|
||||
)
|
||||
const removeShip = useCallback(
|
||||
(ship: ShipProps) => gameProps.removeShip(ship, session?.user.id ?? ""),
|
||||
[gameProps, session?.user.id]
|
||||
)
|
||||
|
||||
return { ships, setShips, removeShip }
|
||||
}
|
||||
|
||||
export default useShips
|
|
@ -1,9 +1,9 @@
|
|||
import { isAuthenticated } from "../pages/start"
|
||||
import { useGameProps } from "./useGameProps"
|
||||
import useIndex from "./useIndex"
|
||||
import { socket } from "@lib/socket"
|
||||
import { GamePropsSchema } from "@lib/zodSchemas"
|
||||
import status from "http-status"
|
||||
import { useSession } from "next-auth/react"
|
||||
import { useRouter } from "next/router"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { toast } from "react-toastify"
|
||||
|
@ -11,6 +11,7 @@ import { toast } from "react-toastify"
|
|||
/** This function should only be called once per page, otherwise there will be multiple socket connections and duplicate event listeners. */
|
||||
function useSocket() {
|
||||
const [isConnectedState, setIsConnectedState] = useState(false)
|
||||
const { selfIndex } = useIndex()
|
||||
const {
|
||||
payload,
|
||||
userStates,
|
||||
|
@ -20,28 +21,25 @@ function useSocket() {
|
|||
setIsReady,
|
||||
gameState,
|
||||
setIsConnected,
|
||||
setActiveIndex,
|
||||
DispatchMove,
|
||||
setShips,
|
||||
} = useGameProps()
|
||||
const { data: session } = useSession()
|
||||
const router = useRouter()
|
||||
|
||||
const { i, isIndex } = useMemo(() => {
|
||||
const i = payload?.users.findIndex((user) => session?.user?.id === user?.id)
|
||||
const isIndex = !(i === undefined || i < 0)
|
||||
if (!isIndex) return { i: undefined, isIndex }
|
||||
return { i, isIndex }
|
||||
}, [payload?.users, session?.user?.id])
|
||||
const isConnected = useMemo(
|
||||
() => (isIndex ? userStates[i].isConnected : isConnectedState),
|
||||
[i, isConnectedState, isIndex, userStates]
|
||||
() =>
|
||||
selfIndex >= 0 ? userStates[selfIndex].isConnected : isConnectedState,
|
||||
[selfIndex, isConnectedState, userStates]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isIndex) return
|
||||
if (selfIndex < 0) return
|
||||
setIsConnected({
|
||||
i,
|
||||
i: selfIndex,
|
||||
isConnected: isConnectedState,
|
||||
})
|
||||
}, [i, isConnectedState, isIndex, setIsConnected])
|
||||
}, [selfIndex, isConnectedState, setIsConnected])
|
||||
|
||||
useEffect(() => {
|
||||
socket.on("connect", () => {
|
||||
|
@ -97,7 +95,7 @@ function useSocket() {
|
|||
i,
|
||||
isConnected: true,
|
||||
})
|
||||
socket.emit("isReady", userStates[i].isReady)
|
||||
socket.emit("isReady", userStates[selfIndex].isReady)
|
||||
message = "Player has joined the lobby."
|
||||
break
|
||||
|
||||
|
@ -122,6 +120,12 @@ function useSocket() {
|
|||
|
||||
socket.on("gameState", gameState)
|
||||
|
||||
socket.on("dispatchMove", DispatchMove)
|
||||
|
||||
socket.on("activeIndex", (i) => setActiveIndex(i, selfIndex))
|
||||
|
||||
socket.on("ships", setShips)
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
console.log("disconnect")
|
||||
setIsConnectedState(false)
|
||||
|
@ -131,13 +135,17 @@ function useSocket() {
|
|||
socket.removeAllListeners()
|
||||
}
|
||||
}, [
|
||||
DispatchMove,
|
||||
full,
|
||||
gameState,
|
||||
router,
|
||||
selfIndex,
|
||||
setActiveIndex,
|
||||
setIsConnected,
|
||||
setIsReady,
|
||||
setPlayer,
|
||||
setSetting,
|
||||
setShips,
|
||||
userStates,
|
||||
])
|
||||
|
||||
|
@ -150,7 +158,7 @@ function useSocket() {
|
|||
.then(isAuthenticated)
|
||||
.then((game) => GamePropsSchema.parse(game))
|
||||
.then((res) => full(res))
|
||||
.catch()
|
||||
.catch((e) => console.log(e))
|
||||
return
|
||||
}
|
||||
if (isConnected) return
|
||||
|
@ -162,7 +170,10 @@ function useSocket() {
|
|||
})
|
||||
}, [full, isConnected, payload?.game?.id])
|
||||
|
||||
return { isConnected: isIndex ? userStates[i].isConnected : isConnectedState }
|
||||
return {
|
||||
isConnected:
|
||||
selfIndex >= 0 ? userStates[selfIndex].isConnected : isConnectedState,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSocket
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { DrawLineProps, ShipProps } from "./frontend"
|
||||
import { MoveDispatchProps, DrawLineProps, ShipProps } from "./frontend"
|
||||
import { GameSettings } from "@components/Lobby/SettingsFrame/Setting"
|
||||
import { GamePropsSchema, PlayerSchema } from "@lib/zodSchemas"
|
||||
import { GameState, Ship } from "@prisma/client"
|
||||
import { GameState } from "@prisma/client"
|
||||
import type { Server as HTTPServer } from "http"
|
||||
import type { Socket as NetSocket } from "net"
|
||||
import type { NextApiResponse } from "next"
|
||||
|
@ -50,7 +50,9 @@ export interface ServerToClientEvents {
|
|||
"draw-line": (props: DrawLineProps, userIndex: number) => void
|
||||
"canvas-clear": () => void
|
||||
gameState: (newState: GameState) => void
|
||||
ships: (ships: ShipProps[], userId: string) => void
|
||||
ships: (ships: ShipProps[], index: number) => void
|
||||
activeIndex: (index: number) => void
|
||||
dispatchMove: (props: MoveDispatchProps, i: number) => void
|
||||
}
|
||||
|
||||
export interface ClientToServerEvents {
|
||||
|
@ -66,6 +68,7 @@ export interface ClientToServerEvents {
|
|||
"canvas-clear": () => void
|
||||
gameState: (newState: GameState) => void
|
||||
ships: (ships: ShipProps[]) => void
|
||||
dispatchMove: (props: MoveDispatchProps) => void
|
||||
}
|
||||
|
||||
interface InterServerEvents {
|
||||
|
|
|
@ -7,10 +7,13 @@ export interface Position {
|
|||
}
|
||||
export interface Target extends Position {
|
||||
show: boolean
|
||||
}
|
||||
export interface TargetPreview extends Target {
|
||||
orientation: Orientation
|
||||
}
|
||||
export interface TargetPreview extends Target {}
|
||||
export interface PointerProps extends TargetList {
|
||||
show: boolean
|
||||
imply: boolean
|
||||
}
|
||||
export interface MouseCursor extends Position {
|
||||
shouldShow: boolean
|
||||
}
|
||||
|
@ -28,14 +31,16 @@ export interface ItemProps {
|
|||
amount?: number
|
||||
iconColor?: string
|
||||
disabled?: boolean
|
||||
enabled?: boolean
|
||||
callback?: () => void
|
||||
}
|
||||
export interface EventBarModes {
|
||||
main: ItemProps[]
|
||||
menu: ItemProps[]
|
||||
actions: ItemProps[]
|
||||
moves: ItemProps[]
|
||||
draw: ItemProps[]
|
||||
settings: ItemProps[]
|
||||
surrender: ItemProps[]
|
||||
}
|
||||
export interface Field extends Position {
|
||||
field: string
|
||||
|
@ -60,7 +65,8 @@ export interface ShipProps extends Position {
|
|||
export interface IndexedPosition extends Position {
|
||||
i?: number
|
||||
}
|
||||
export interface ActionDispatchProps extends Position {
|
||||
index?: number
|
||||
action: MoveType
|
||||
export interface MoveDispatchProps extends Position {
|
||||
index: number
|
||||
type: MoveType
|
||||
orientation: Orientation
|
||||
}
|
||||
|
|
|
@ -2,14 +2,13 @@ import type {
|
|||
Hit,
|
||||
IndexedPosition,
|
||||
Mode,
|
||||
PointerProps,
|
||||
Position,
|
||||
ShipProps,
|
||||
Target,
|
||||
TargetList,
|
||||
TargetPreview,
|
||||
} from "../../interfaces/frontend"
|
||||
import { count } from "@components/Gamefield/Gamefield"
|
||||
import { PointerProps } from "@components/Gamefield/GamefieldPointer"
|
||||
import { Orientation } from "@prisma/client"
|
||||
|
||||
export function borderCN(count: number, x: number, y: number) {
|
||||
|
@ -30,7 +29,7 @@ export function fieldIndex(count: number, x: number, y: number) {
|
|||
return y * (count + 2) + x
|
||||
}
|
||||
|
||||
const modes: Mode[] = [
|
||||
export const modes: Mode[] = [
|
||||
{
|
||||
pointerGrid: Array.from(Array(1), () => Array.from(Array(1))),
|
||||
type: "missile",
|
||||
|
@ -59,8 +58,12 @@ export function isAlreadyHit(x: number, y: number, hits: Hit[]) {
|
|||
|
||||
export function targetList(
|
||||
{ x: targetX, y: targetY }: Position,
|
||||
mode: number
|
||||
modeInput: number | string
|
||||
): TargetList[] {
|
||||
const mode =
|
||||
typeof modeInput === "number"
|
||||
? modeInput
|
||||
: modes.findIndex((e) => e.type === modeInput)
|
||||
if (mode < 0) return []
|
||||
const { pointerGrid, type } = modes[mode]
|
||||
const xLength = pointerGrid.length
|
||||
|
@ -112,6 +115,7 @@ export const initlialTarget = {
|
|||
x: 2,
|
||||
y: 2,
|
||||
show: false,
|
||||
orientation: Orientation.h,
|
||||
}
|
||||
export const initlialTargetPreview = {
|
||||
x: 2,
|
||||
|
|
|
@ -16,11 +16,11 @@ export const PlayerSchema = z
|
|||
.array(),
|
||||
moves: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
index: z.number(),
|
||||
action: z.nativeEnum(MoveType),
|
||||
type: z.nativeEnum(MoveType),
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
orientation: z.nativeEnum(Orientation),
|
||||
})
|
||||
.array(),
|
||||
ships: z
|
||||
|
@ -32,6 +32,13 @@ export const PlayerSchema = z
|
|||
orientation: z.nativeEnum(Orientation),
|
||||
})
|
||||
.array(),
|
||||
hits: z
|
||||
.object({
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
hit: z.boolean(),
|
||||
})
|
||||
.array(),
|
||||
})
|
||||
.nullable()
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ export default async function create(
|
|||
users: {
|
||||
create: {
|
||||
userId: id,
|
||||
index: 1,
|
||||
index: 0,
|
||||
chats: {
|
||||
create: {
|
||||
event: "created",
|
||||
|
|
|
@ -64,7 +64,7 @@ export default async function join(
|
|||
data: {
|
||||
gameId: game.id,
|
||||
userId: id,
|
||||
index: 2,
|
||||
index: 1,
|
||||
},
|
||||
select: {
|
||||
game: gameSelects,
|
||||
|
|
|
@ -34,9 +34,8 @@ export const gameSelects = {
|
|||
},
|
||||
moves: {
|
||||
select: {
|
||||
id: true,
|
||||
index: true,
|
||||
action: true,
|
||||
type: true,
|
||||
x: true,
|
||||
y: true,
|
||||
orientation: true,
|
||||
|
@ -51,6 +50,13 @@ export const gameSelects = {
|
|||
orientation: true,
|
||||
},
|
||||
},
|
||||
hits: {
|
||||
select: {
|
||||
x: true,
|
||||
y: true,
|
||||
hit: true,
|
||||
},
|
||||
},
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
|
@ -102,10 +108,17 @@ export function composeBody(
|
|||
...user,
|
||||
}))
|
||||
.sort((user1, user2) => user1.index - user2.index)
|
||||
let activeIndex = undefined
|
||||
if (game.state === "running") {
|
||||
const l1 = game.users[0].moves.length
|
||||
const l2 = game.users[1].moves.length
|
||||
activeIndex = l1 > l2 ? 1 : 0
|
||||
}
|
||||
const payload = {
|
||||
game: game,
|
||||
gamePin: gamePin?.pin ?? null,
|
||||
users,
|
||||
activeIndex,
|
||||
}
|
||||
return getPayloadwithChecksum(payload)
|
||||
}
|
||||
|
|
|
@ -214,10 +214,17 @@ const SocketHandler = async (
|
|||
},
|
||||
})
|
||||
io.to(socket.data.gameId).emit("gameState", newState)
|
||||
if (newState === "running")
|
||||
io.to(socket.data.gameId).emit("activeIndex", 0)
|
||||
})
|
||||
|
||||
socket.on("ships", async (ships) => {
|
||||
if (!socket.data.gameId || !socket.data.user?.id) return
|
||||
if (
|
||||
!socket.data.gameId ||
|
||||
!socket.data.user?.id ||
|
||||
typeof socket.data.index === "undefined"
|
||||
)
|
||||
return
|
||||
await prisma.user_Game.update({
|
||||
where: {
|
||||
gameId_userId: {
|
||||
|
@ -234,7 +241,38 @@ const SocketHandler = async (
|
|||
},
|
||||
},
|
||||
})
|
||||
socket.to(socket.data.gameId).emit("ships", ships, socket.data.user.id)
|
||||
socket.to(socket.data.gameId).emit("ships", ships, socket.data.index)
|
||||
})
|
||||
|
||||
socket.on("dispatchMove", async (props) => {
|
||||
if (
|
||||
!socket.data.gameId ||
|
||||
!socket.data.user?.id ||
|
||||
typeof socket.data.index === "undefined"
|
||||
)
|
||||
return
|
||||
const user_Game = await prisma.user_Game
|
||||
.update({
|
||||
where: {
|
||||
gameId_userId: {
|
||||
gameId: socket.data.gameId,
|
||||
userId: socket.data.user?.id,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
moves: {
|
||||
create: props,
|
||||
},
|
||||
},
|
||||
select: { game: gameSelects },
|
||||
})
|
||||
.catch((e) => console.log(e, props))
|
||||
if (!user_Game?.game) return
|
||||
const game = user_Game.game
|
||||
const l1 = game.users[0].moves.length
|
||||
const l2 = game.users[1].moves.length
|
||||
io.to(socket.data.gameId).emit("dispatchMove", props, socket.data.index)
|
||||
io.to(socket.data.gameId).emit("activeIndex", l1 > l2 ? 1 : 0)
|
||||
})
|
||||
|
||||
socket.on("disconnecting", async () => {
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import BurgerMenu from "@components/BurgerMenu"
|
||||
import Logo from "@components/Logo"
|
||||
import { faCirclePlay } from "@fortawesome/pro-thin-svg-icons"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { useRouter } from "next/router"
|
||||
|
||||
export default function Home() {
|
||||
|
@ -12,11 +10,11 @@ export default function Home() {
|
|||
<div className="mx-auto flex h-full max-w-screen-md flex-col items-center justify-evenly">
|
||||
<Logo />
|
||||
<BurgerMenu />
|
||||
<div className="flex h-36 w-64 items-center justify-center rounded-xl border-4 border-black bg-[#2227] sm:h-48 sm:w-96 md:h-72 md:w-[32rem] md:border-[6px] xl:h-[26rem] xl:w-[48rem]">
|
||||
<FontAwesomeIcon
|
||||
className="text-6xl sm:text-7xl md:text-8xl"
|
||||
icon={faCirclePlay}
|
||||
/>
|
||||
<div className="flex h-36 w-64 items-center justify-center overflow-hidden rounded-xl border-8 border-black bg-[#2227] sm:h-48 sm:w-96 md:h-72 md:w-[32rem] md:border-[6px] xl:h-[26rem] xl:w-[48rem]">
|
||||
<video controls>
|
||||
<source src="/Regelwerk.mp4" type="video/mp4" />
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
</div>
|
||||
<button
|
||||
className="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"
|
||||
|
|
|
@ -73,9 +73,9 @@ export default function Start() {
|
|||
.then(isAuthenticated)
|
||||
.then((game) => GamePropsSchema.parse(game))
|
||||
|
||||
const action = !pin ? "erstellt" : "angefragt"
|
||||
const move = !pin ? "erstellt" : "angefragt"
|
||||
const toastId = "pageLoad"
|
||||
toast("Raum wird " + action, {
|
||||
toast("Raum wird " + move, {
|
||||
icon: Icons.spinner(),
|
||||
toastId,
|
||||
autoClose: false,
|
||||
|
@ -150,14 +150,14 @@ export default function Start() {
|
|||
</button>
|
||||
<div className="flex flex-col items-center gap-6 sm:gap-12">
|
||||
<OptionButton
|
||||
action={() => gameFetch()}
|
||||
callback={() => gameFetch()}
|
||||
icon={faPlus}
|
||||
disabled={!session}
|
||||
>
|
||||
Raum erstellen
|
||||
</OptionButton>
|
||||
<OptionButton
|
||||
action={() => {
|
||||
callback={() => {
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: { q: "join" },
|
||||
|
@ -185,7 +185,7 @@ export default function Start() {
|
|||
</OptionButton>
|
||||
<OptionButton
|
||||
icon={faEye}
|
||||
action={() => {
|
||||
callback={() => {
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: { q: "watch" },
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -68,27 +68,12 @@ model VerificationToken {
|
|||
@@map("verificationtokens")
|
||||
}
|
||||
|
||||
enum Orientation {
|
||||
h
|
||||
v
|
||||
}
|
||||
|
||||
model Ship {
|
||||
id String @id @default(cuid())
|
||||
size Int
|
||||
variant Int
|
||||
x Int
|
||||
y Int
|
||||
orientation Orientation
|
||||
user_GameId String
|
||||
User_Game User_Game @relation(fields: [user_GameId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
enum GameState {
|
||||
lobby
|
||||
starting
|
||||
running
|
||||
ended
|
||||
aborted
|
||||
}
|
||||
|
||||
model Game {
|
||||
|
@ -112,6 +97,31 @@ model Gamepin {
|
|||
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
enum Orientation {
|
||||
h
|
||||
v
|
||||
}
|
||||
|
||||
model Ship {
|
||||
id String @id @default(cuid())
|
||||
size Int
|
||||
variant Int
|
||||
x Int
|
||||
y Int
|
||||
orientation Orientation
|
||||
user_GameId String
|
||||
User_Game User_Game @relation(fields: [user_GameId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model Hit {
|
||||
id String @id @default(cuid())
|
||||
x Int
|
||||
y Int
|
||||
hit Boolean
|
||||
user_GameId String
|
||||
User_Game User_Game @relation(fields: [user_GameId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model User_Game {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now())
|
||||
|
@ -120,6 +130,7 @@ model User_Game {
|
|||
index Int
|
||||
moves Move[]
|
||||
ships Ship[]
|
||||
hits Hit[]
|
||||
chats Chat[]
|
||||
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
@ -129,25 +140,22 @@ model User_Game {
|
|||
}
|
||||
|
||||
enum MoveType {
|
||||
radar
|
||||
htorpedo
|
||||
vtorpedo
|
||||
missile
|
||||
vtorpedo
|
||||
htorpedo
|
||||
radar
|
||||
}
|
||||
|
||||
model Move {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now())
|
||||
index Int
|
||||
action MoveType
|
||||
type MoveType
|
||||
x Int
|
||||
y Int
|
||||
orientation Orientation
|
||||
user_game_id String
|
||||
user_game User_Game @relation(fields: [user_game_id], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([user_game_id, index])
|
||||
@@unique([action, x, y])
|
||||
}
|
||||
|
||||
model Chat {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue