Working ship placement

This commit is contained in:
aronmal 2023-06-11 22:09:36 +02:00
parent 0317a3343c
commit c2af2dffa2
Signed by: aronmal
GPG key ID: 816B7707426FC612
14 changed files with 1155 additions and 281 deletions

View file

@ -1,9 +1,15 @@
import { EventBarModes } from "../../interfaces/frontend" import { EventBarModes } from "../../interfaces/frontend"
import Item from "./Item" import Item from "./Item"
import { GameSettings } from "@components/Lobby/SettingsFrame/Setting" import { GameSettings } from "@components/Lobby/SettingsFrame/Setting"
import {
faSquare2,
faSquare3,
faSquare4,
} from "@fortawesome/pro-regular-svg-icons"
import { import {
faArrowRightFromBracket, faArrowRightFromBracket,
faBroomWide, faBroomWide,
faCheck,
faComments, faComments,
faEye, faEye,
faEyeSlash, faEyeSlash,
@ -11,6 +17,7 @@ import {
faPalette, faPalette,
faReply, faReply,
faScribble, faScribble,
faShip,
faSparkles, faSparkles,
faSwords, faSwords,
} from "@fortawesome/pro-solid-svg-icons" } from "@fortawesome/pro-solid-svg-icons"
@ -18,13 +25,13 @@ import { useDrawProps } from "@hooks/useDrawProps"
import { useGameProps } from "@hooks/useGameProps" import { useGameProps } from "@hooks/useGameProps"
import { socket } from "@lib/socket" import { socket } from "@lib/socket"
import { GamePropsSchema } from "@lib/zodSchemas" import { GamePropsSchema } from "@lib/zodSchemas"
import { useSession } from "next-auth/react"
import { import {
Dispatch, Dispatch,
SetStateAction, SetStateAction,
useCallback, useCallback,
useEffect, useEffect,
useMemo, useMemo,
useState,
} from "react" } from "react"
export function setGameSetting( export function setGameSetting(
@ -42,23 +49,35 @@ export function setGameSetting(
} }
} }
function EventBar({ function EventBar({ clear }: { clear: () => void }) {
props: { setMode, clear },
}: {
props: {
setMode: Dispatch<SetStateAction<number>>
clear: () => void
}
}) {
const [menu, setMenu] = useState<keyof EventBarModes>("main")
const { shouldHide, color } = useDrawProps() 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( const gameSetting = useCallback(
(payload: GameSettings) => setGameSetting(payload, setSetting, full), (payload: GameSettings) => setGameSetting(payload, setSetting, full),
[full, setSetting] [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>( const items = useMemo<EventBarModes>(
() => ({ () => ({
main: [ main: [
@ -69,13 +88,21 @@ function EventBar({
setMenu("menu") setMenu("menu")
}, },
}, },
{ payload?.game?.state === "running"
icon: faSwords, ? {
text: "Attack", icon: faSwords,
callback: () => { text: "Attack",
setMenu("attack") callback: () => {
}, setMenu("actions")
}, },
}
: {
icon: faShip,
text: "Ships",
callback: () => {
setMenu("actions")
},
},
{ {
icon: "pen", icon: "pen",
text: "Draw", text: "Draw",
@ -92,14 +119,6 @@ function EventBar({
}, },
], ],
menu: [ menu: [
{
icon: faReply,
text: "Return",
iconColor: "#555",
callback: () => {
setMenu("main")
},
},
{ {
icon: faArrowRightFromBracket, icon: faArrowRightFromBracket,
text: "Leave", text: "Leave",
@ -109,51 +128,84 @@ function EventBar({
}, },
}, },
], ],
attack: [ actions:
{ payload?.game?.state === "running"
icon: faReply, ? [
text: "Return", {
iconColor: "#555", icon: "scope",
callback: () => { text: "Fire missile",
setMenu("main") callback: () => {
}, setMode(3)
}, setTarget((e) => ({ ...e, show: false }))
{ },
icon: "radar", },
text: "Radar scan", {
amount: 1, icon: "torpedo",
callback: () => { text: "Fire torpedo",
setMode(0) amount:
setTarget((e) => ({ ...e, show: false })) 2 -
}, (self?.moves.filter(
}, (e) => e.action === "htorpedo" || e.action === "vtorpedo"
{ ).length ?? 0),
icon: "torpedo", callback: () => {
text: "Fire torpedo", setMode(1)
amount: 1, setTarget((e) => ({ ...e, show: false }))
callback: () => { },
setMode(1) },
setTarget((e) => ({ ...e, show: false })) {
}, icon: "radar",
}, text: "Radar scan",
{ amount:
icon: "scope", 1 -
text: "Fire missile", (self?.moves.filter((e) => e.action === "radar").length ?? 0),
callback: () => { callback: () => {
setMode(3) setMode(0)
setTarget((e) => ({ ...e, show: false })) 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: [ draw: [
{
icon: faReply,
text: "Return",
iconColor: "#555",
callback: () => {
setMenu("main")
},
},
{ icon: faBroomWide, text: "Clear", callback: clear }, { icon: faBroomWide, text: "Clear", callback: clear },
{ icon: faPalette, text: "Color", iconColor: color }, { icon: faPalette, text: "Color", iconColor: color },
{ {
@ -165,14 +217,6 @@ function EventBar({
}, },
], ],
settings: [ settings: [
{
icon: faReply,
text: "Return",
iconColor: "#555",
callback: () => {
setMenu("main")
},
},
{ {
icon: faGlasses, icon: faGlasses,
text: "Spectators", text: "Spectators",
@ -209,22 +253,54 @@ function EventBar({
clear, clear,
color, color,
gameSetting, gameSetting,
mode,
payload?.game?.allowChat, payload?.game?.allowChat,
payload?.game?.allowMarkDraw, payload?.game?.allowMarkDraw,
payload?.game?.allowSpecials, payload?.game?.allowSpecials,
payload?.game?.allowSpectators, payload?.game?.allowSpectators,
payload?.game?.state,
payload?.users,
self?.moves,
session?.user?.id,
setIsReady,
setMenu,
setMode, setMode,
setTarget, setTarget,
ships,
shouldHide, 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(() => { useEffect(() => {
useDrawProps.setState({ enable: menu === "draw" }) useDrawProps.setState({ enable: menu === "draw" })
}, [menu]) }, [menu])
return ( return (
<div className="event-bar"> <div className="event-bar">
{menu !== "main" && (
<Item
props={{
icon: faReply,
text: "Return",
iconColor: "#555",
callback: () => {
setMenu("main")
},
}}
></Item>
)}
{items[menu].map((e, i) => ( {items[menu].map((e, i) => (
<Item key={i} props={e} /> <Item key={i} props={e} />
))} ))}

View file

@ -10,11 +10,14 @@ 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 { useGameProps } from "@hooks/useGameProps"
import useSocket from "@hooks/useSocket"
import { socket } from "@lib/socket"
import { import {
initlialMouseCursor, initlialMouseCursor,
overlapsWithAnyBorder, overlapsWithAnyBorder,
isAlreadyHit, isAlreadyHit,
targetList, targetList,
shipProps,
} from "@lib/utils/helpers" } from "@lib/utils/helpers"
import { CSSProperties, useCallback } from "react" import { CSSProperties, useCallback } from "react"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
@ -25,40 +28,61 @@ function Gamefield() {
const [mouseCursor, setMouseCursor] = const [mouseCursor, setMouseCursor] =
useState<MouseCursor>(initlialMouseCursor) useState<MouseCursor>(initlialMouseCursor)
const { const {
mode,
hits, hits,
target, target,
targetPreview, targetPreview,
DispatchHits, ships,
addShip,
payload,
DispatchAction,
setTarget, setTarget,
setTargetPreview, setTargetPreview,
full,
} = useGameProps() } = 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( const settingTarget = useCallback(
(isGameTile: boolean, x: number, y: number) => { (isGameTile: boolean, x: number, y: number) => {
const list = targetList(targetPreview, mode) if (payload?.game?.state === "running") {
if ( const list = targetList(targetPreview, mode)
!isGameTile || if (
!list.filter(({ x, y }) => !isAlreadyHit(x, y, hits)).length !isGameTile ||
) !list.filter(({ x, y }) => !isAlreadyHit(x, y, hits)).length
return )
if (target.show && target.x == x && target.y == y) { return
DispatchHits({ if (target.show && target.x == x && target.y == y) {
type: "fireMissile", DispatchAction({
payload: list.map(({ x, y }) => ({ action: "missile",
hit: isAlreadyHit(x, y, hits), ...target,
x, })
y, setTarget((t) => ({ ...t, show: false }))
})), } else if (!overlapsWithAnyBorder(targetPreview, mode))
}) setTarget({ show: true, x, y })
setTarget((t) => ({ ...t, show: false })) } else if (payload?.game?.state === "starting") {
} else if (!overlapsWithAnyBorder(targetPreview, mode)) addShip(shipProps(ships, mode, targetPreview))
setTarget({ show: true, x, y }) }
}, },
[DispatchHits, hits, mode, setTarget, target, targetPreview] [
DispatchAction,
addShip,
hits,
mode,
payload?.game?.state,
setTarget,
ships,
target,
targetPreview,
]
) )
useEffect(() => { useEffect(() => {
if (mode < 0) return
const { x, y, show } = target const { x, y, show } = target
const { shouldShow, ...position } = mouseCursor const { shouldShow, ...position } = mouseCursor
if (!shouldShow || overlapsWithAnyBorder(position, mode)) if (!shouldShow || overlapsWithAnyBorder(position, mode))
@ -97,8 +121,7 @@ function Gamefield() {
{/* Fog images */} {/* Fog images */}
{/* <FogImages /> */} {/* <FogImages /> */}
<Targets props={{ target, targetPreview, mode, hits }} /> <Targets />
{/* <span id='dev-debug' style={{gridArea: '1 / 12 / 1 / 15', backgroundColor: 'red', zIndex: 3} as CSSProperties}>Debug</span> */}
<canvas <canvas
style={ style={
@ -114,7 +137,7 @@ function Gamefield() {
height="648" height="648"
/> />
</div> </div>
<EventBar props={{ setMode, clear }} /> <EventBar clear={clear} />
</div> </div>
) )
} }

View file

@ -45,11 +45,12 @@ function Item({
)} )}
<div <div
className={classNames("container", { className={classNames("container", {
amount: amount, amount: typeof amount !== "undefined",
disabled: disabled, disabled: disabled || amount === 0,
enabled: disabled === false,
})} })}
style={ style={
amount typeof amount !== "undefined"
? ({ ? ({
"--amount": JSON.stringify(amount.toString()), "--amount": JSON.stringify(amount.toString()),
} as CSSProperties) } as CSSProperties)

View 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

View file

@ -1,31 +1,23 @@
import { Ship } from "../../interfaces/frontend" import Ship from "./Ship"
import classNames from "classnames" import { useGameProps } from "@hooks/useGameProps"
import { CSSProperties } from "react"
function Ships() { function Ships() {
const shipIndexes: Ship[] = [ // const shipIndexes: Ship[] = [
{ size: 2, variant: 1, x: 3, y: 3 }, // { size: 2, variant: 1, x: 3, y: 3 },
{ size: 3, variant: 1, x: 4, y: 3 }, // { size: 3, variant: 1, x: 4, y: 3 },
{ size: 3, variant: 2, x: 5, y: 3 }, // { size: 3, variant: 2, x: 5, y: 3 },
{ size: 3, variant: 3, x: 6, y: 3 }, // { size: 3, variant: 3, x: 6, y: 3 },
{ size: 4, variant: 1, x: 7, y: 3 }, // { size: 4, variant: 1, x: 7, y: 3 },
{ size: 4, variant: 2, x: 8, y: 3 }, // { size: 4, variant: 2, x: 8, y: 3 },
] // ]
const { ships } = useGameProps()
return ( return (
<> <>
{shipIndexes.map(({ size, variant, x, y }, i) => { {ships.map((props, i) => (
const filename = `ship_blue_${size}x_${variant}.gif` <Ship key={i} props={props} />
return ( ))}
<div
key={i}
className={classNames("ship", "s" + size)}
style={{ "--x": x, "--y": y } as CSSProperties}
>
<img src={"/assets/" + filename} alt={filename} />
</div>
)
})}
</> </>
) )
} }

View file

@ -1,30 +1,27 @@
import { Hit, Target } from "../../interfaces/frontend"
import GamefieldPointer from "./GamefieldPointer" import GamefieldPointer from "./GamefieldPointer"
import { composeTargetTiles } from "@lib/utils/helpers" import Ship from "./Ship"
import React from "react" import { useGameProps } from "@hooks/useGameProps"
import { composeTargetTiles, shipProps } from "@lib/utils/helpers"
function Targets({ function Targets() {
props: { target, targetPreview, mode, hits }, const { payload, target, targetPreview, mode, hits, ships } = useGameProps()
}: {
props: { if (payload?.game?.state === "running")
target: Target return (
targetPreview: Target <>
mode: number {[
hits: Hit[] ...composeTargetTiles(target, mode, hits).map((props, i) => (
} <GamefieldPointer key={"t" + i} props={props} />
}) { )),
return ( ...composeTargetTiles(targetPreview, mode, hits).map((props, i) => (
<> <GamefieldPointer key={"p" + i} props={props} preview />
{[ )),
...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 export default Targets

View file

@ -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 { GameSettings } from "@components/Lobby/SettingsFrame/Setting"
import { getPayloadwithChecksum } from "@lib/getPayloadwithChecksum" import { getPayloadwithChecksum } from "@lib/getPayloadwithChecksum"
import { socket } from "@lib/socket" import { socket } from "@lib/socket"
@ -19,12 +25,18 @@ const initialState: optionalGamePropsSchema & {
isReady: boolean isReady: boolean
isConnected: boolean isConnected: boolean
}[] }[]
menu: keyof EventBarModes
mode: number
ships: ShipProps[]
hits: Hit[] hits: Hit[]
target: Target target: Target
targetPreview: Target targetPreview: Target
} = { } = {
menu: "actions",
mode: 0,
payload: null, payload: null,
hash: null, hash: null,
ships: [],
hits: [], hits: [],
target: initlialTarget, target: initlialTarget,
targetPreview: initlialTargetPreview, targetPreview: initlialTargetPreview,
@ -37,7 +49,7 @@ const initialState: optionalGamePropsSchema & {
export type State = typeof initialState export type State = typeof initialState
export type Action = { export type Action = {
DispatchHits: (action: HitDispatch) => void DispatchAction: (props: ActionDispatchProps) => void
setTarget: (target: SetStateAction<Target>) => void setTarget: (target: SetStateAction<Target>) => void
setTargetPreview: (targetPreview: SetStateAction<Target>) => void setTargetPreview: (targetPreview: SetStateAction<Target>) => void
setPlayer: (payload: { users: PlayerSchema[] }) => string | null setPlayer: (payload: { users: PlayerSchema[] }) => string | null
@ -46,6 +58,8 @@ export type Action = {
leave: (cb: () => void) => void leave: (cb: () => void) => void
setIsReady: (payload: { i: number; isReady: boolean }) => void setIsReady: (payload: { i: number; isReady: boolean }) => void
starting: () => void starting: () => void
addShip: (props: ShipProps) => void
removeShip: (props: ShipProps) => void
setIsConnected: (payload: { i: number; isConnected: boolean }) => void setIsConnected: (payload: { i: number; isConnected: boolean }) => void
reset: () => void reset: () => void
} }
@ -54,16 +68,16 @@ export const useGameProps = create<State & Action>()(
devtools( devtools(
(set) => ({ (set) => ({
...initialState, ...initialState,
DispatchHits: (action) => DispatchAction: (action) =>
set( set(
produce((state: State) => { produce((state: State) => {
switch (action.type) { // switch (action.type) {
case "fireMissile": // case "fireMissile":
case "htorpedo": // case "htorpedo":
case "vtorpedo": { // case "vtorpedo": {
state.hits.push(...action.payload) // state.hits.push(...action.payload)
} // }
} // }
}) })
), ),
setTarget: (target) => setTarget: (target) =>
@ -82,6 +96,25 @@ export const useGameProps = create<State & Action>()(
else state.targetPreview = targetPreview 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) => { setPlayer: (payload) => {
let hash: string | null = null let hash: string | null = null
set( set(
@ -103,7 +136,6 @@ export const useGameProps = create<State & Action>()(
return hash return hash
}, },
setSetting: (settings) => { setSetting: (settings) => {
const payload = JSON.stringify(settings)
let hash: string | null = null let hash: string | null = null
set( set(
produce((state: State) => { produce((state: State) => {

View file

@ -1,4 +1,5 @@
import { IconDefinition } from "@fortawesome/pro-solid-svg-icons" import { IconDefinition } from "@fortawesome/pro-solid-svg-icons"
import { MoveType } from "@prisma/client"
export interface Position { export interface Position {
x: number x: number
@ -16,7 +17,7 @@ export interface TargetList extends Position {
} }
export interface Mode { export interface Mode {
pointerGrid: any[][] pointerGrid: any[][]
type: string type: MoveType
} }
export interface ItemProps { export interface ItemProps {
icon: string | IconDefinition icon: string | IconDefinition
@ -29,7 +30,7 @@ export interface ItemProps {
export interface EventBarModes { export interface EventBarModes {
main: ItemProps[] main: ItemProps[]
menu: ItemProps[] menu: ItemProps[]
attack: ItemProps[] actions: ItemProps[]
draw: ItemProps[] draw: ItemProps[]
settings: ItemProps[] settings: ItemProps[]
} }
@ -40,29 +41,7 @@ export interface Hit extends Position {
hit: boolean hit: boolean
} }
interface fireMissile { export interface Point extends Position {}
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 DrawLineProps { export interface DrawLineProps {
currentPoint: Point currentPoint: Point
@ -74,7 +53,12 @@ export interface Draw extends DrawLineProps {
ctx: CanvasRenderingContext2D ctx: CanvasRenderingContext2D
} }
export interface Ship extends Position { export interface ShipProps extends Position {
size: number size: number
variant: number variant: number
} }
export interface ActionDispatchProps extends Position {
index?: number
action: MoveType
}

View file

@ -1,13 +1,14 @@
import type { import type {
Hit, Hit,
HitDispatch,
Mode, Mode,
Position, Position,
ShipProps,
Target, Target,
TargetList, TargetList,
} from "../../interfaces/frontend" } from "../../interfaces/frontend"
import { count } from "@components/Gamefield/Gamefield" import { count } from "@components/Gamefield/Gamefield"
import { PointerProps } from "@components/Gamefield/GamefieldPointer" import { PointerProps } from "@components/Gamefield/GamefieldPointer"
import { useGameProps } from "@hooks/useGameProps"
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"
@ -54,13 +55,6 @@ export function isAlreadyHit(x: number, y: number, hits: Hit[]) {
return !!hits.filter((h) => h.x === x && h.y === y).length 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( export function targetList(
{ x: targetX, y: targetY }: Position, { x: targetX, y: targetY }: Position,
mode: number mode: number
@ -129,3 +123,21 @@ export const initlialMouseCursor = {
x: 0, x: 0,
y: 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,
})

View file

@ -1,3 +1,4 @@
import { ShipSchema } from "../prisma/generated/zod"
import { GameState } from "@prisma/client" import { GameState } from "@prisma/client"
import { z } from "zod" import { z } from "zod"
@ -18,6 +19,9 @@ export const PlayerSchema = z
.object({ .object({
id: z.string(), id: z.string(),
index: z.number(), index: z.number(),
action: z.string(),
x: z.number(),
y: z.number(),
}) })
.array(), .array(),
}) })
@ -34,10 +38,20 @@ export const CreateSchema = z.object({
allowSpecials: z.boolean(), allowSpecials: z.boolean(),
allowChat: z.boolean(), allowChat: z.boolean(),
allowMarkDraw: 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(), .nullable(),
gamePin: z.string().nullable(), gamePin: z.string().nullable(),
users: PlayerSchema.array(), users: PlayerSchema.array(),
activeIndex: z.number().optional(),
}) })
export const GamePropsSchema = z.object({ export const GamePropsSchema = z.object({

View file

@ -15,6 +15,15 @@ export const gameSelects = {
allowSpecials: true, allowSpecials: true,
allowSpectators: true, allowSpectators: true,
state: true, state: true,
ships: {
select: {
id: true,
size: true,
variant: true,
x: true,
y: true,
},
},
gamePin: { gamePin: {
select: { select: {
pin: true, pin: true,
@ -36,6 +45,9 @@ export const gameSelects = {
select: { select: {
id: true, id: true,
index: true, index: true,
action: true,
x: true,
y: true,
}, },
}, },
user: { user: {

File diff suppressed because it is too large Load diff

View file

@ -75,11 +75,22 @@ enum GameState {
ended 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 { model Game {
id String @id @default(cuid()) id String @id @default(cuid())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
state GameState @default(lobby) state GameState @default(lobby)
ships Ship[]
allowSpectators Boolean @default(true) allowSpectators Boolean @default(true)
allowSpecials Boolean @default(true) allowSpecials Boolean @default(true)
allowChat Boolean @default(true) allowChat Boolean @default(true)
@ -111,14 +122,25 @@ model User_Game {
@@unique([gameId, userId]) @@unique([gameId, userId])
} }
enum MoveType {
radar
htorpedo
vtorpedo
missile
}
model Move { model Move {
id String @id @default(cuid()) id String @id @default(cuid())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
index Int index Int
action MoveType
x Int
y Int
user_game_id String user_game_id String
user_game User_Game @relation(fields: [user_game_id], references: [id], onDelete: Cascade) user_game User_Game @relation(fields: [user_game_id], references: [id], onDelete: Cascade)
@@unique([user_game_id, index]) @@unique([user_game_id, index])
@@unique([action, x, y])
} }
model Chat { model Chat {

View file

@ -108,7 +108,7 @@ body {
position: relative; position: relative;
@include flex-col; @include flex-col;
align-items: center; align-items: center;
grid-row: var(--x); grid-row: var(--y);
pointer-events: none; pointer-events: none;
img { img {
@ -119,16 +119,31 @@ body {
// object-fit: cover; // 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 { &.s2 {
grid-column: var(--y) / 5; grid-column: var(--x) / calc(var(--x) + 2);
} }
&.s3 { &.s3 {
grid-column: var(--y) / 6; grid-column: var(--x) / calc(var(--x) + 3);
} }
&.s4 { &.s4 {
grid-column: var(--y) / 7; grid-column: var(--x) / calc(var(--x) + 4);
} }
} }
@ -285,8 +300,8 @@ body {
&.left { &.left {
transform: rotate(90deg); transform: rotate(90deg);
animation: floatInl 3s ease-out; animation: floatInLe 3s ease-out;
@keyframes floatInl { @keyframes floatInLe {
from { from {
transform: scale(2) rotate(90deg); transform: scale(2) rotate(90deg);
margin-left: -324px; margin-left: -324px;
@ -297,8 +312,8 @@ body {
&.right { &.right {
transform: rotate(270deg); transform: rotate(270deg);
animation: floatInr 3s ease-out; animation: floatInRi 3s ease-out;
@keyframes floatInr { @keyframes floatInRi {
from { from {
transform: scale(2) rotate(270deg); transform: scale(2) rotate(270deg);
margin-left: 324px; margin-left: 324px;
@ -307,8 +322,8 @@ body {
} }
} }
&.top { &.top {
animation: floatInt 3s ease-out; animation: floatInTop 3s ease-out;
@keyframes floatInt { @keyframes floatInTop {
from { from {
transform: scale(2); transform: scale(2);
margin-top: 648px; margin-top: 648px;
@ -319,8 +334,8 @@ body {
&.bottom { &.bottom {
transform: rotate(180deg); transform: rotate(180deg);
animation: floatInb 3s ease-out; animation: floatInBot 3s ease-out;
@keyframes floatInb { @keyframes floatInBot {
from { from {
transform: scale(2) rotate(180deg); transform: scale(2) rotate(180deg);
margin-top: 0; margin-top: 0;
@ -331,12 +346,18 @@ body {
&.middle { &.middle {
grid-area: 4 / 4 / -4 / -4; grid-area: 4 / 4 / -4 / -4;
transform: scale(1.5);
animation: floatInMid 3s ease-in-out;
@keyframes floatInMid {
from {
opacity: 0;
}
}
} }
} }
canvas { canvas {
grid-area: 1 / 1 / -1 / -1; grid-area: 1 / 1 / -1 / -1;
border: 1px solid #000;
border-radius: 0.5rem; border-radius: 0.5rem;
z-index: 1; z-index: 1;
} }
@ -379,6 +400,9 @@ body {
background-color: white; background-color: white;
border-radius: 1rem; border-radius: 1rem;
&.enabled {
box-shadow: 0 0 0.5rem 0.25rem royalblue;
}
&.disabled { &.disabled {
box-shadow: inset 0 0 1rem 1rem #888; box-shadow: inset 0 0 1rem 1rem #888;
} }