Working ship placement
This commit is contained in:
parent
0317a3343c
commit
c2af2dffa2
14 changed files with 1155 additions and 281 deletions
|
@ -1,9 +1,15 @@
|
|||
import { EventBarModes } from "../../interfaces/frontend"
|
||||
import Item from "./Item"
|
||||
import { GameSettings } from "@components/Lobby/SettingsFrame/Setting"
|
||||
import {
|
||||
faSquare2,
|
||||
faSquare3,
|
||||
faSquare4,
|
||||
} from "@fortawesome/pro-regular-svg-icons"
|
||||
import {
|
||||
faArrowRightFromBracket,
|
||||
faBroomWide,
|
||||
faCheck,
|
||||
faComments,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
|
@ -11,6 +17,7 @@ import {
|
|||
faPalette,
|
||||
faReply,
|
||||
faScribble,
|
||||
faShip,
|
||||
faSparkles,
|
||||
faSwords,
|
||||
} from "@fortawesome/pro-solid-svg-icons"
|
||||
|
@ -18,13 +25,13 @@ import { useDrawProps } from "@hooks/useDrawProps"
|
|||
import { useGameProps } from "@hooks/useGameProps"
|
||||
import { socket } from "@lib/socket"
|
||||
import { GamePropsSchema } from "@lib/zodSchemas"
|
||||
import { useSession } from "next-auth/react"
|
||||
import {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react"
|
||||
|
||||
export function setGameSetting(
|
||||
|
@ -42,23 +49,35 @@ export function setGameSetting(
|
|||
}
|
||||
}
|
||||
|
||||
function EventBar({
|
||||
props: { setMode, clear },
|
||||
}: {
|
||||
props: {
|
||||
setMode: Dispatch<SetStateAction<number>>
|
||||
clear: () => void
|
||||
}
|
||||
}) {
|
||||
const [menu, setMenu] = useState<keyof EventBarModes>("main")
|
||||
function EventBar({ clear }: { clear: () => void }) {
|
||||
const { shouldHide, color } = useDrawProps()
|
||||
const { payload, setSetting, full, setTarget } = useGameProps()
|
||||
|
||||
const { data: session } = useSession()
|
||||
const {
|
||||
ships,
|
||||
payload,
|
||||
menu,
|
||||
mode,
|
||||
setSetting,
|
||||
full,
|
||||
setTarget,
|
||||
setIsReady,
|
||||
} = useGameProps()
|
||||
const gameSetting = useCallback(
|
||||
(payload: GameSettings) => setGameSetting(payload, setSetting, full),
|
||||
[full, setSetting]
|
||||
)
|
||||
|
||||
const setMenu = useCallback(
|
||||
(menu: keyof EventBarModes) => useGameProps.setState({ menu }),
|
||||
[]
|
||||
)
|
||||
const setMode = useCallback(
|
||||
(mode: number) => useGameProps.setState({ mode }),
|
||||
[]
|
||||
)
|
||||
const self = useMemo(
|
||||
() => payload?.users.find((e) => e?.id === session?.user.id),
|
||||
[payload?.users, session?.user.id]
|
||||
)
|
||||
const items = useMemo<EventBarModes>(
|
||||
() => ({
|
||||
main: [
|
||||
|
@ -69,13 +88,21 @@ function EventBar({
|
|||
setMenu("menu")
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: faSwords,
|
||||
text: "Attack",
|
||||
callback: () => {
|
||||
setMenu("attack")
|
||||
},
|
||||
},
|
||||
payload?.game?.state === "running"
|
||||
? {
|
||||
icon: faSwords,
|
||||
text: "Attack",
|
||||
callback: () => {
|
||||
setMenu("actions")
|
||||
},
|
||||
}
|
||||
: {
|
||||
icon: faShip,
|
||||
text: "Ships",
|
||||
callback: () => {
|
||||
setMenu("actions")
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: "pen",
|
||||
text: "Draw",
|
||||
|
@ -92,14 +119,6 @@ function EventBar({
|
|||
},
|
||||
],
|
||||
menu: [
|
||||
{
|
||||
icon: faReply,
|
||||
text: "Return",
|
||||
iconColor: "#555",
|
||||
callback: () => {
|
||||
setMenu("main")
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: faArrowRightFromBracket,
|
||||
text: "Leave",
|
||||
|
@ -109,51 +128,84 @@ function EventBar({
|
|||
},
|
||||
},
|
||||
],
|
||||
attack: [
|
||||
{
|
||||
icon: faReply,
|
||||
text: "Return",
|
||||
iconColor: "#555",
|
||||
callback: () => {
|
||||
setMenu("main")
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: "radar",
|
||||
text: "Radar scan",
|
||||
amount: 1,
|
||||
callback: () => {
|
||||
setMode(0)
|
||||
setTarget((e) => ({ ...e, show: false }))
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: "torpedo",
|
||||
text: "Fire torpedo",
|
||||
amount: 1,
|
||||
callback: () => {
|
||||
setMode(1)
|
||||
setTarget((e) => ({ ...e, show: false }))
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: "scope",
|
||||
text: "Fire missile",
|
||||
callback: () => {
|
||||
setMode(3)
|
||||
setTarget((e) => ({ ...e, show: false }))
|
||||
},
|
||||
},
|
||||
],
|
||||
actions:
|
||||
payload?.game?.state === "running"
|
||||
? [
|
||||
{
|
||||
icon: "scope",
|
||||
text: "Fire missile",
|
||||
callback: () => {
|
||||
setMode(3)
|
||||
setTarget((e) => ({ ...e, show: false }))
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: "torpedo",
|
||||
text: "Fire torpedo",
|
||||
amount:
|
||||
2 -
|
||||
(self?.moves.filter(
|
||||
(e) => e.action === "htorpedo" || e.action === "vtorpedo"
|
||||
).length ?? 0),
|
||||
callback: () => {
|
||||
setMode(1)
|
||||
setTarget((e) => ({ ...e, show: false }))
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: "radar",
|
||||
text: "Radar scan",
|
||||
amount:
|
||||
1 -
|
||||
(self?.moves.filter((e) => e.action === "radar").length ?? 0),
|
||||
callback: () => {
|
||||
setMode(0)
|
||||
setTarget((e) => ({ ...e, show: false }))
|
||||
},
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
icon: faSquare2,
|
||||
text: "Minensucher",
|
||||
amount: 1 - ships.filter((e) => e.size === 2).length,
|
||||
callback: () => {
|
||||
if (1 - ships.filter((e) => e.size === 2).length === 0) return
|
||||
setMode(0)
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: faSquare3,
|
||||
text: "Kreuzer",
|
||||
amount: 3 - ships.filter((e) => e.size === 3).length,
|
||||
callback: () => {
|
||||
if (3 - ships.filter((e) => e.size === 3).length === 0) return
|
||||
setMode(1)
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: faSquare4,
|
||||
text: "Schlachtschiff",
|
||||
amount: 2 - ships.filter((e) => e.size === 4).length,
|
||||
callback: () => {
|
||||
if (2 - ships.filter((e) => e.size === 4).length === 0) return
|
||||
setMode(2)
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: faCheck,
|
||||
text: "Done",
|
||||
disabled: mode >= 0,
|
||||
callback: () => {
|
||||
const i = payload?.users.findIndex(
|
||||
(user) => session?.user?.id === user?.id
|
||||
)
|
||||
if (!i) return
|
||||
setIsReady({ isReady: true, i })
|
||||
},
|
||||
},
|
||||
],
|
||||
draw: [
|
||||
{
|
||||
icon: faReply,
|
||||
text: "Return",
|
||||
iconColor: "#555",
|
||||
callback: () => {
|
||||
setMenu("main")
|
||||
},
|
||||
},
|
||||
{ icon: faBroomWide, text: "Clear", callback: clear },
|
||||
{ icon: faPalette, text: "Color", iconColor: color },
|
||||
{
|
||||
|
@ -165,14 +217,6 @@ function EventBar({
|
|||
},
|
||||
],
|
||||
settings: [
|
||||
{
|
||||
icon: faReply,
|
||||
text: "Return",
|
||||
iconColor: "#555",
|
||||
callback: () => {
|
||||
setMenu("main")
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: faGlasses,
|
||||
text: "Spectators",
|
||||
|
@ -209,22 +253,54 @@ function EventBar({
|
|||
clear,
|
||||
color,
|
||||
gameSetting,
|
||||
mode,
|
||||
payload?.game?.allowChat,
|
||||
payload?.game?.allowMarkDraw,
|
||||
payload?.game?.allowSpecials,
|
||||
payload?.game?.allowSpectators,
|
||||
payload?.game?.state,
|
||||
payload?.users,
|
||||
self?.moves,
|
||||
session?.user?.id,
|
||||
setIsReady,
|
||||
setMenu,
|
||||
setMode,
|
||||
setTarget,
|
||||
ships,
|
||||
shouldHide,
|
||||
]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
menu !== "actions" ||
|
||||
payload?.game?.state !== "starting" ||
|
||||
mode < 0 ||
|
||||
items.actions[mode].amount
|
||||
)
|
||||
return
|
||||
const index = items.actions.findIndex((e) => e.amount)
|
||||
useGameProps.setState({ mode: index })
|
||||
}, [items.actions, menu, mode, payload?.game?.state])
|
||||
|
||||
useEffect(() => {
|
||||
useDrawProps.setState({ enable: menu === "draw" })
|
||||
}, [menu])
|
||||
|
||||
return (
|
||||
<div className="event-bar">
|
||||
{menu !== "main" && (
|
||||
<Item
|
||||
props={{
|
||||
icon: faReply,
|
||||
text: "Return",
|
||||
iconColor: "#555",
|
||||
callback: () => {
|
||||
setMenu("main")
|
||||
},
|
||||
}}
|
||||
></Item>
|
||||
)}
|
||||
{items[menu].map((e, i) => (
|
||||
<Item key={i} props={e} />
|
||||
))}
|
||||
|
|
|
@ -10,11 +10,14 @@ import Targets from "@components/Gamefield/Targets"
|
|||
import { useDraw } from "@hooks/useDraw"
|
||||
import { useDrawProps } from "@hooks/useDrawProps"
|
||||
import { useGameProps } from "@hooks/useGameProps"
|
||||
import useSocket from "@hooks/useSocket"
|
||||
import { socket } from "@lib/socket"
|
||||
import {
|
||||
initlialMouseCursor,
|
||||
overlapsWithAnyBorder,
|
||||
isAlreadyHit,
|
||||
targetList,
|
||||
shipProps,
|
||||
} from "@lib/utils/helpers"
|
||||
import { CSSProperties, useCallback } from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
|
@ -25,40 +28,61 @@ function Gamefield() {
|
|||
const [mouseCursor, setMouseCursor] =
|
||||
useState<MouseCursor>(initlialMouseCursor)
|
||||
const {
|
||||
mode,
|
||||
hits,
|
||||
target,
|
||||
targetPreview,
|
||||
DispatchHits,
|
||||
ships,
|
||||
addShip,
|
||||
payload,
|
||||
DispatchAction,
|
||||
setTarget,
|
||||
setTargetPreview,
|
||||
full,
|
||||
} = useGameProps()
|
||||
const [mode, setMode] = useState(0)
|
||||
const { isConnected } = useSocket()
|
||||
|
||||
useEffect(() => {
|
||||
if (payload?.game?.id || !isConnected) return
|
||||
socket.emit("update", full)
|
||||
}, [full, payload?.game?.id, isConnected])
|
||||
|
||||
const settingTarget = useCallback(
|
||||
(isGameTile: boolean, x: number, y: number) => {
|
||||
const list = targetList(targetPreview, mode)
|
||||
if (
|
||||
!isGameTile ||
|
||||
!list.filter(({ x, y }) => !isAlreadyHit(x, y, hits)).length
|
||||
)
|
||||
return
|
||||
if (target.show && target.x == x && target.y == y) {
|
||||
DispatchHits({
|
||||
type: "fireMissile",
|
||||
payload: list.map(({ x, y }) => ({
|
||||
hit: isAlreadyHit(x, y, hits),
|
||||
x,
|
||||
y,
|
||||
})),
|
||||
})
|
||||
setTarget((t) => ({ ...t, show: false }))
|
||||
} else if (!overlapsWithAnyBorder(targetPreview, mode))
|
||||
setTarget({ show: true, x, y })
|
||||
if (payload?.game?.state === "running") {
|
||||
const list = targetList(targetPreview, mode)
|
||||
if (
|
||||
!isGameTile ||
|
||||
!list.filter(({ x, y }) => !isAlreadyHit(x, y, hits)).length
|
||||
)
|
||||
return
|
||||
if (target.show && target.x == x && target.y == y) {
|
||||
DispatchAction({
|
||||
action: "missile",
|
||||
...target,
|
||||
})
|
||||
setTarget((t) => ({ ...t, show: false }))
|
||||
} else if (!overlapsWithAnyBorder(targetPreview, mode))
|
||||
setTarget({ show: true, x, y })
|
||||
} else if (payload?.game?.state === "starting") {
|
||||
addShip(shipProps(ships, mode, targetPreview))
|
||||
}
|
||||
},
|
||||
[DispatchHits, hits, mode, setTarget, target, targetPreview]
|
||||
[
|
||||
DispatchAction,
|
||||
addShip,
|
||||
hits,
|
||||
mode,
|
||||
payload?.game?.state,
|
||||
setTarget,
|
||||
ships,
|
||||
target,
|
||||
targetPreview,
|
||||
]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (mode < 0) return
|
||||
const { x, y, show } = target
|
||||
const { shouldShow, ...position } = mouseCursor
|
||||
if (!shouldShow || overlapsWithAnyBorder(position, mode))
|
||||
|
@ -97,8 +121,7 @@ function Gamefield() {
|
|||
{/* Fog images */}
|
||||
{/* <FogImages /> */}
|
||||
|
||||
<Targets props={{ target, targetPreview, mode, hits }} />
|
||||
{/* <span id='dev-debug' style={{gridArea: '1 / 12 / 1 / 15', backgroundColor: 'red', zIndex: 3} as CSSProperties}>Debug</span> */}
|
||||
<Targets />
|
||||
|
||||
<canvas
|
||||
style={
|
||||
|
@ -114,7 +137,7 @@ function Gamefield() {
|
|||
height="648"
|
||||
/>
|
||||
</div>
|
||||
<EventBar props={{ setMode, clear }} />
|
||||
<EventBar clear={clear} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -45,11 +45,12 @@ function Item({
|
|||
)}
|
||||
<div
|
||||
className={classNames("container", {
|
||||
amount: amount,
|
||||
disabled: disabled,
|
||||
amount: typeof amount !== "undefined",
|
||||
disabled: disabled || amount === 0,
|
||||
enabled: disabled === false,
|
||||
})}
|
||||
style={
|
||||
amount
|
||||
typeof amount !== "undefined"
|
||||
? ({
|
||||
"--amount": JSON.stringify(amount.toString()),
|
||||
} as CSSProperties)
|
||||
|
|
34
leaky-ships/components/Gamefield/Ship.tsx
Normal file
34
leaky-ships/components/Gamefield/Ship.tsx
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { ShipProps } from "../../interfaces/frontend"
|
||||
import { useGameProps } from "@hooks/useGameProps"
|
||||
import classNames from "classnames"
|
||||
import React, { CSSProperties } from "react"
|
||||
|
||||
function Ship({
|
||||
props: { size, variant, x, y },
|
||||
preview,
|
||||
}: {
|
||||
props: ShipProps
|
||||
preview?: boolean
|
||||
}) {
|
||||
const { payload, removeShip } = useGameProps()
|
||||
const filename = `ship_blue_${size}x_${variant}.gif`
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames("ship", "s" + size, {
|
||||
preview: preview,
|
||||
interactive: payload?.game?.state === "starting",
|
||||
})}
|
||||
style={{ "--x": x, "--y": y } as CSSProperties}
|
||||
onClick={() => {
|
||||
if (payload?.game?.state !== "starting") return
|
||||
removeShip({ size, variant, x, y })
|
||||
useGameProps.setState({ mode: size - 2 })
|
||||
}}
|
||||
>
|
||||
<img src={"/assets/" + filename} alt={filename} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Ship
|
|
@ -1,31 +1,23 @@
|
|||
import { Ship } from "../../interfaces/frontend"
|
||||
import classNames from "classnames"
|
||||
import { CSSProperties } from "react"
|
||||
import Ship from "./Ship"
|
||||
import { useGameProps } from "@hooks/useGameProps"
|
||||
|
||||
function Ships() {
|
||||
const shipIndexes: Ship[] = [
|
||||
{ size: 2, variant: 1, x: 3, y: 3 },
|
||||
{ size: 3, variant: 1, x: 4, y: 3 },
|
||||
{ size: 3, variant: 2, x: 5, y: 3 },
|
||||
{ size: 3, variant: 3, x: 6, y: 3 },
|
||||
{ size: 4, variant: 1, x: 7, y: 3 },
|
||||
{ size: 4, variant: 2, x: 8, y: 3 },
|
||||
]
|
||||
// const shipIndexes: Ship[] = [
|
||||
// { size: 2, variant: 1, x: 3, y: 3 },
|
||||
// { size: 3, variant: 1, x: 4, y: 3 },
|
||||
// { size: 3, variant: 2, x: 5, y: 3 },
|
||||
// { size: 3, variant: 3, x: 6, y: 3 },
|
||||
// { size: 4, variant: 1, x: 7, y: 3 },
|
||||
// { size: 4, variant: 2, x: 8, y: 3 },
|
||||
// ]
|
||||
|
||||
const { ships } = useGameProps()
|
||||
|
||||
return (
|
||||
<>
|
||||
{shipIndexes.map(({ size, variant, x, y }, i) => {
|
||||
const filename = `ship_blue_${size}x_${variant}.gif`
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className={classNames("ship", "s" + size)}
|
||||
style={{ "--x": x, "--y": y } as CSSProperties}
|
||||
>
|
||||
<img src={"/assets/" + filename} alt={filename} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
{ships.map((props, i) => (
|
||||
<Ship key={i} props={props} />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,30 +1,27 @@
|
|||
import { Hit, Target } from "../../interfaces/frontend"
|
||||
import GamefieldPointer from "./GamefieldPointer"
|
||||
import { composeTargetTiles } from "@lib/utils/helpers"
|
||||
import React from "react"
|
||||
import Ship from "./Ship"
|
||||
import { useGameProps } from "@hooks/useGameProps"
|
||||
import { composeTargetTiles, shipProps } from "@lib/utils/helpers"
|
||||
|
||||
function Targets({
|
||||
props: { target, targetPreview, mode, hits },
|
||||
}: {
|
||||
props: {
|
||||
target: Target
|
||||
targetPreview: Target
|
||||
mode: number
|
||||
hits: Hit[]
|
||||
}
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{[
|
||||
...composeTargetTiles(target, mode, hits).map((props, i) => (
|
||||
<GamefieldPointer key={"t" + i} props={props} />
|
||||
)),
|
||||
...composeTargetTiles(targetPreview, mode, hits).map((props, i) => (
|
||||
<GamefieldPointer key={"p" + i} props={props} preview />
|
||||
)),
|
||||
]}
|
||||
</>
|
||||
)
|
||||
function Targets() {
|
||||
const { payload, target, targetPreview, mode, hits, ships } = useGameProps()
|
||||
|
||||
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) => (
|
||||
<GamefieldPointer key={"p" + i} props={props} preview />
|
||||
)),
|
||||
]}
|
||||
</>
|
||||
)
|
||||
|
||||
if (payload?.game?.state === "starting" && mode >= 0 && targetPreview.show)
|
||||
return <Ship preview props={shipProps(ships, mode, targetPreview)} />
|
||||
}
|
||||
|
||||
export default Targets
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { Hit, HitDispatch, Target } from "../interfaces/frontend"
|
||||
import {
|
||||
ActionDispatchProps,
|
||||
EventBarModes,
|
||||
Hit,
|
||||
ShipProps,
|
||||
Target,
|
||||
} from "../interfaces/frontend"
|
||||
import { GameSettings } from "@components/Lobby/SettingsFrame/Setting"
|
||||
import { getPayloadwithChecksum } from "@lib/getPayloadwithChecksum"
|
||||
import { socket } from "@lib/socket"
|
||||
|
@ -19,12 +25,18 @@ const initialState: optionalGamePropsSchema & {
|
|||
isReady: boolean
|
||||
isConnected: boolean
|
||||
}[]
|
||||
menu: keyof EventBarModes
|
||||
mode: number
|
||||
ships: ShipProps[]
|
||||
hits: Hit[]
|
||||
target: Target
|
||||
targetPreview: Target
|
||||
} = {
|
||||
menu: "actions",
|
||||
mode: 0,
|
||||
payload: null,
|
||||
hash: null,
|
||||
ships: [],
|
||||
hits: [],
|
||||
target: initlialTarget,
|
||||
targetPreview: initlialTargetPreview,
|
||||
|
@ -37,7 +49,7 @@ const initialState: optionalGamePropsSchema & {
|
|||
export type State = typeof initialState
|
||||
|
||||
export type Action = {
|
||||
DispatchHits: (action: HitDispatch) => void
|
||||
DispatchAction: (props: ActionDispatchProps) => void
|
||||
setTarget: (target: SetStateAction<Target>) => void
|
||||
setTargetPreview: (targetPreview: SetStateAction<Target>) => void
|
||||
setPlayer: (payload: { users: PlayerSchema[] }) => string | null
|
||||
|
@ -46,6 +58,8 @@ export type Action = {
|
|||
leave: (cb: () => void) => void
|
||||
setIsReady: (payload: { i: number; isReady: boolean }) => void
|
||||
starting: () => void
|
||||
addShip: (props: ShipProps) => void
|
||||
removeShip: (props: ShipProps) => void
|
||||
setIsConnected: (payload: { i: number; isConnected: boolean }) => void
|
||||
reset: () => void
|
||||
}
|
||||
|
@ -54,16 +68,16 @@ export const useGameProps = create<State & Action>()(
|
|||
devtools(
|
||||
(set) => ({
|
||||
...initialState,
|
||||
DispatchHits: (action) =>
|
||||
DispatchAction: (action) =>
|
||||
set(
|
||||
produce((state: State) => {
|
||||
switch (action.type) {
|
||||
case "fireMissile":
|
||||
case "htorpedo":
|
||||
case "vtorpedo": {
|
||||
state.hits.push(...action.payload)
|
||||
}
|
||||
}
|
||||
// switch (action.type) {
|
||||
// case "fireMissile":
|
||||
// case "htorpedo":
|
||||
// case "vtorpedo": {
|
||||
// state.hits.push(...action.payload)
|
||||
// }
|
||||
// }
|
||||
})
|
||||
),
|
||||
setTarget: (target) =>
|
||||
|
@ -82,6 +96,25 @@ export const useGameProps = create<State & Action>()(
|
|||
else state.targetPreview = targetPreview
|
||||
})
|
||||
),
|
||||
addShip: (props) =>
|
||||
set(
|
||||
produce((state: State) => {
|
||||
state.ships.push(props)
|
||||
})
|
||||
),
|
||||
removeShip: ({ size, variant, x, y }) =>
|
||||
set(
|
||||
produce((state: State) => {
|
||||
const indexToRemove = state.ships.findIndex(
|
||||
(ship) =>
|
||||
ship.size === size &&
|
||||
ship.variant === variant &&
|
||||
ship.x === x &&
|
||||
ship.y === y
|
||||
)
|
||||
state.ships.splice(indexToRemove, 1)
|
||||
})
|
||||
),
|
||||
setPlayer: (payload) => {
|
||||
let hash: string | null = null
|
||||
set(
|
||||
|
@ -103,7 +136,6 @@ export const useGameProps = create<State & Action>()(
|
|||
return hash
|
||||
},
|
||||
setSetting: (settings) => {
|
||||
const payload = JSON.stringify(settings)
|
||||
let hash: string | null = null
|
||||
set(
|
||||
produce((state: State) => {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { IconDefinition } from "@fortawesome/pro-solid-svg-icons"
|
||||
import { MoveType } from "@prisma/client"
|
||||
|
||||
export interface Position {
|
||||
x: number
|
||||
|
@ -16,7 +17,7 @@ export interface TargetList extends Position {
|
|||
}
|
||||
export interface Mode {
|
||||
pointerGrid: any[][]
|
||||
type: string
|
||||
type: MoveType
|
||||
}
|
||||
export interface ItemProps {
|
||||
icon: string | IconDefinition
|
||||
|
@ -29,7 +30,7 @@ export interface ItemProps {
|
|||
export interface EventBarModes {
|
||||
main: ItemProps[]
|
||||
menu: ItemProps[]
|
||||
attack: ItemProps[]
|
||||
actions: ItemProps[]
|
||||
draw: ItemProps[]
|
||||
settings: ItemProps[]
|
||||
}
|
||||
|
@ -40,29 +41,7 @@ export interface Hit extends Position {
|
|||
hit: boolean
|
||||
}
|
||||
|
||||
interface fireMissile {
|
||||
type: "fireMissile" | "htorpedo" | "vtorpedo"
|
||||
payload: {
|
||||
x: number
|
||||
y: number
|
||||
hit: boolean
|
||||
}[]
|
||||
}
|
||||
interface removeMissile {
|
||||
type: "removeMissile"
|
||||
payload: {
|
||||
x: number
|
||||
y: number
|
||||
hit: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export type HitDispatch = fireMissile | removeMissile
|
||||
|
||||
export interface Point {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
export interface Point extends Position {}
|
||||
|
||||
export interface DrawLineProps {
|
||||
currentPoint: Point
|
||||
|
@ -74,7 +53,12 @@ export interface Draw extends DrawLineProps {
|
|||
ctx: CanvasRenderingContext2D
|
||||
}
|
||||
|
||||
export interface Ship extends Position {
|
||||
export interface ShipProps extends Position {
|
||||
size: number
|
||||
variant: number
|
||||
}
|
||||
|
||||
export interface ActionDispatchProps extends Position {
|
||||
index?: number
|
||||
action: MoveType
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import type {
|
||||
Hit,
|
||||
HitDispatch,
|
||||
Mode,
|
||||
Position,
|
||||
ShipProps,
|
||||
Target,
|
||||
TargetList,
|
||||
} from "../../interfaces/frontend"
|
||||
import { count } from "@components/Gamefield/Gamefield"
|
||||
import { PointerProps } from "@components/Gamefield/GamefieldPointer"
|
||||
import { useGameProps } from "@hooks/useGameProps"
|
||||
|
||||
export function borderCN(count: number, x: number, y: number) {
|
||||
if (x === 0) return "left"
|
||||
|
@ -54,13 +55,6 @@ export function isAlreadyHit(x: number, y: number, hits: Hit[]) {
|
|||
return !!hits.filter((h) => h.x === x && h.y === y).length
|
||||
}
|
||||
|
||||
function isSet(x: number, y: number, targetList: TargetList[], show: boolean) {
|
||||
return (
|
||||
!!targetList.filter((field) => x === field.x && y === field.y).length &&
|
||||
show
|
||||
)
|
||||
}
|
||||
|
||||
export function targetList(
|
||||
{ x: targetX, y: targetY }: Position,
|
||||
mode: number
|
||||
|
@ -129,3 +123,21 @@ export const initlialMouseCursor = {
|
|||
x: 0,
|
||||
y: 0,
|
||||
}
|
||||
|
||||
export const shipProps = (
|
||||
ships: ShipProps[],
|
||||
mode: number,
|
||||
targetPreview: Target
|
||||
) => ({
|
||||
size: mode + 2,
|
||||
variant:
|
||||
ships
|
||||
.filter((e) => e.size === mode + 2)
|
||||
.sort((a, b) => a.variant - b.variant)
|
||||
.reduce((prev, curr) => {
|
||||
console.log(curr.variant - prev)
|
||||
return curr.variant - prev < 2 ? curr.variant : prev
|
||||
}, 0) + 1,
|
||||
x: targetPreview.x - Math.floor((mode + 2) / 2),
|
||||
y: targetPreview.y,
|
||||
})
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { ShipSchema } from "../prisma/generated/zod"
|
||||
import { GameState } from "@prisma/client"
|
||||
import { z } from "zod"
|
||||
|
||||
|
@ -18,6 +19,9 @@ export const PlayerSchema = z
|
|||
.object({
|
||||
id: z.string(),
|
||||
index: z.number(),
|
||||
action: z.string(),
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
})
|
||||
.array(),
|
||||
})
|
||||
|
@ -34,10 +38,20 @@ export const CreateSchema = z.object({
|
|||
allowSpecials: z.boolean(),
|
||||
allowChat: z.boolean(),
|
||||
allowMarkDraw: z.boolean(),
|
||||
ships: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
size: z.number(),
|
||||
variant: z.number(),
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
})
|
||||
.array(),
|
||||
})
|
||||
.nullable(),
|
||||
gamePin: z.string().nullable(),
|
||||
users: PlayerSchema.array(),
|
||||
activeIndex: z.number().optional(),
|
||||
})
|
||||
|
||||
export const GamePropsSchema = z.object({
|
||||
|
|
|
@ -15,6 +15,15 @@ export const gameSelects = {
|
|||
allowSpecials: true,
|
||||
allowSpectators: true,
|
||||
state: true,
|
||||
ships: {
|
||||
select: {
|
||||
id: true,
|
||||
size: true,
|
||||
variant: true,
|
||||
x: true,
|
||||
y: true,
|
||||
},
|
||||
},
|
||||
gamePin: {
|
||||
select: {
|
||||
pin: true,
|
||||
|
@ -36,6 +45,9 @@ export const gameSelects = {
|
|||
select: {
|
||||
id: true,
|
||||
index: true,
|
||||
action: true,
|
||||
x: true,
|
||||
y: true,
|
||||
},
|
||||
},
|
||||
user: {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -75,11 +75,22 @@ enum GameState {
|
|||
ended
|
||||
}
|
||||
|
||||
model Ship {
|
||||
id String @id @default(cuid())
|
||||
size Int
|
||||
variant Int
|
||||
x Int
|
||||
y Int
|
||||
gameId String
|
||||
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model Game {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
state GameState @default(lobby)
|
||||
ships Ship[]
|
||||
allowSpectators Boolean @default(true)
|
||||
allowSpecials Boolean @default(true)
|
||||
allowChat Boolean @default(true)
|
||||
|
@ -111,14 +122,25 @@ model User_Game {
|
|||
@@unique([gameId, userId])
|
||||
}
|
||||
|
||||
enum MoveType {
|
||||
radar
|
||||
htorpedo
|
||||
vtorpedo
|
||||
missile
|
||||
}
|
||||
|
||||
model Move {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now())
|
||||
index Int
|
||||
action MoveType
|
||||
x Int
|
||||
y Int
|
||||
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 {
|
||||
|
|
|
@ -108,7 +108,7 @@ body {
|
|||
position: relative;
|
||||
@include flex-col;
|
||||
align-items: center;
|
||||
grid-row: var(--x);
|
||||
grid-row: var(--y);
|
||||
pointer-events: none;
|
||||
|
||||
img {
|
||||
|
@ -119,16 +119,31 @@ body {
|
|||
// object-fit: cover;
|
||||
}
|
||||
|
||||
&.interactive:not(.preview) {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
&.preview {
|
||||
border: 2px dashed orange;
|
||||
border-radius: 0.5rem;
|
||||
animation: blink 0.5s ease-in-out alternate infinite;
|
||||
@keyframes blink {
|
||||
from {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.s2 {
|
||||
grid-column: var(--y) / 5;
|
||||
grid-column: var(--x) / calc(var(--x) + 2);
|
||||
}
|
||||
|
||||
&.s3 {
|
||||
grid-column: var(--y) / 6;
|
||||
grid-column: var(--x) / calc(var(--x) + 3);
|
||||
}
|
||||
|
||||
&.s4 {
|
||||
grid-column: var(--y) / 7;
|
||||
grid-column: var(--x) / calc(var(--x) + 4);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -285,8 +300,8 @@ body {
|
|||
|
||||
&.left {
|
||||
transform: rotate(90deg);
|
||||
animation: floatInl 3s ease-out;
|
||||
@keyframes floatInl {
|
||||
animation: floatInLe 3s ease-out;
|
||||
@keyframes floatInLe {
|
||||
from {
|
||||
transform: scale(2) rotate(90deg);
|
||||
margin-left: -324px;
|
||||
|
@ -297,8 +312,8 @@ body {
|
|||
|
||||
&.right {
|
||||
transform: rotate(270deg);
|
||||
animation: floatInr 3s ease-out;
|
||||
@keyframes floatInr {
|
||||
animation: floatInRi 3s ease-out;
|
||||
@keyframes floatInRi {
|
||||
from {
|
||||
transform: scale(2) rotate(270deg);
|
||||
margin-left: 324px;
|
||||
|
@ -307,8 +322,8 @@ body {
|
|||
}
|
||||
}
|
||||
&.top {
|
||||
animation: floatInt 3s ease-out;
|
||||
@keyframes floatInt {
|
||||
animation: floatInTop 3s ease-out;
|
||||
@keyframes floatInTop {
|
||||
from {
|
||||
transform: scale(2);
|
||||
margin-top: 648px;
|
||||
|
@ -319,8 +334,8 @@ body {
|
|||
|
||||
&.bottom {
|
||||
transform: rotate(180deg);
|
||||
animation: floatInb 3s ease-out;
|
||||
@keyframes floatInb {
|
||||
animation: floatInBot 3s ease-out;
|
||||
@keyframes floatInBot {
|
||||
from {
|
||||
transform: scale(2) rotate(180deg);
|
||||
margin-top: 0;
|
||||
|
@ -331,12 +346,18 @@ body {
|
|||
|
||||
&.middle {
|
||||
grid-area: 4 / 4 / -4 / -4;
|
||||
transform: scale(1.5);
|
||||
animation: floatInMid 3s ease-in-out;
|
||||
@keyframes floatInMid {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
canvas {
|
||||
grid-area: 1 / 1 / -1 / -1;
|
||||
border: 1px solid #000;
|
||||
border-radius: 0.5rem;
|
||||
z-index: 1;
|
||||
}
|
||||
|
@ -379,6 +400,9 @@ body {
|
|||
background-color: white;
|
||||
border-radius: 1rem;
|
||||
|
||||
&.enabled {
|
||||
box-shadow: 0 0 0.5rem 0.25rem royalblue;
|
||||
}
|
||||
&.disabled {
|
||||
box-shadow: inset 0 0 1rem 1rem #888;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue