diff --git a/leaky-ships/components/Gamefield/BorderTiles.tsx b/leaky-ships/components/Gamefield/BorderTiles.tsx index 34c9568..1f2ba88 100644 --- a/leaky-ships/components/Gamefield/BorderTiles.tsx +++ b/leaky-ships/components/Gamefield/BorderTiles.tsx @@ -1,7 +1,16 @@ -import { MouseCursor } from "../../interfaces/frontend" import { count } from "./Gamefield" -import { borderCN, cornerCN, fieldIndex } from "@lib/utils/helpers" -import { CSSProperties, Dispatch, SetStateAction } from "react" +import { useGameProps } from "@hooks/useGameProps" +import { + borderCN, + cornerCN, + fieldIndex, + intersectingShip, + isAlreadyHit, + overlapsWithAnyBorder, + shipProps, + targetList, +} from "@lib/utils/helpers" +import { CSSProperties, useCallback } from "react" type TilesType = { key: number @@ -11,14 +20,61 @@ type TilesType = { y: number } -function BorderTiles({ - props: { settingTarget, setMouseCursor }, -}: { - props: { - settingTarget: (isGameTile: boolean, x: number, y: number) => void - setMouseCursor: Dispatch> - } -}) { +function BorderTiles() { + const { + DispatchAction, + payload, + mode, + ships, + hits, + target, + targetPreview, + mouseCursor, + setTarget, + addShip, + setMouseCursor, + removeShip, + } = useGameProps() + + const settingTarget = useCallback( + (isGameTile: boolean, x: number, y: number) => { + 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" && + targetPreview.show && + !intersectingShip(ships, shipProps(ships, mode, targetPreview)).score + ) { + setMouseCursor((e) => ({ ...e, shouldShow: false })) + addShip(shipProps(ships, mode, targetPreview)) + } + }, + [ + DispatchAction, + addShip, + hits, + mode, + payload?.game?.state, + setMouseCursor, + setTarget, + ships, + target, + targetPreview, + ] + ) let tilesProperties: TilesType[] = [] for (let y = 0; y < count + 2; y++) { @@ -49,9 +105,44 @@ function BorderTiles({ key={key} className={className} style={{ "--x": x, "--y": y } as CSSProperties} - onClick={() => settingTarget(isGameTile, x, y)} + onClick={() => { + if (payload?.game?.state === "running") { + settingTarget(isGameTile, x, y) + } else if (payload?.game?.state === "starting") { + const { index } = intersectingShip(ships, { + ...mouseCursor, + size: 1, + variant: 0, + orientation: "h", + }) + if (typeof index === "undefined") + settingTarget(isGameTile, x, y) + else { + const ship = ships[index] + useGameProps.setState({ mode: ship.size - 2 }) + removeShip(ship) + setMouseCursor((e) => ({ ...e, shouldShow: true })) + } + } + }} onMouseEnter={() => - setMouseCursor({ x, y, shouldShow: isGameTile }) + setMouseCursor({ + x, + y, + shouldShow: + isGameTile && + (payload?.game?.state === "starting" + ? intersectingShip( + ships, + shipProps(ships, mode, { + x, + y, + orientation: targetPreview.orientation, + }), + true + ).score < 2 + : true), + }) } > ) diff --git a/leaky-ships/components/Gamefield/EventBar.tsx b/leaky-ships/components/Gamefield/EventBar.tsx index 1bd01bb..ae780b1 100644 --- a/leaky-ships/components/Gamefield/EventBar.tsx +++ b/leaky-ships/components/Gamefield/EventBar.tsx @@ -16,6 +16,7 @@ import { faGlasses, faPalette, faReply, + faRotate, faScribble, faShip, faSparkles, @@ -25,6 +26,7 @@ import { useDrawProps } from "@hooks/useDrawProps" import { useGameProps } from "@hooks/useGameProps" import { socket } from "@lib/socket" import { GamePropsSchema } from "@lib/zodSchemas" +import { Orientation } from "@prisma/client" import { useSession } from "next-auth/react" import { Dispatch, @@ -60,20 +62,13 @@ function EventBar({ clear }: { clear: () => void }) { setSetting, full, setTarget, + setTargetPreview, 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] @@ -85,7 +80,7 @@ function EventBar({ clear }: { clear: () => void }) { icon: "burger-menu", text: "Menu", callback: () => { - setMenu("menu") + useGameProps.setState({ menu: "menu" }) }, }, payload?.game?.state === "running" @@ -93,28 +88,28 @@ function EventBar({ clear }: { clear: () => void }) { icon: faSwords, text: "Attack", callback: () => { - setMenu("actions") + useGameProps.setState({ menu: "actions" }) }, } : { icon: faShip, text: "Ships", callback: () => { - setMenu("actions") + useGameProps.setState({ menu: "actions" }) }, }, { icon: "pen", text: "Draw", callback: () => { - setMenu("draw") + useGameProps.setState({ menu: "draw" }) }, }, { icon: "gear", text: "Settings", callback: () => { - setMenu("settings") + useGameProps.setState({ menu: "settings" }) }, }, ], @@ -135,7 +130,7 @@ function EventBar({ clear }: { clear: () => void }) { icon: "scope", text: "Fire missile", callback: () => { - setMode(3) + useGameProps.setState({ mode: 3 }) setTarget((e) => ({ ...e, show: false })) }, }, @@ -148,7 +143,7 @@ function EventBar({ clear }: { clear: () => void }) { (e) => e.action === "htorpedo" || e.action === "vtorpedo" ).length ?? 0), callback: () => { - setMode(1) + useGameProps.setState({ mode: 1 }) setTarget((e) => ({ ...e, show: false })) }, }, @@ -159,7 +154,7 @@ function EventBar({ clear }: { clear: () => void }) { 1 - (self?.moves.filter((e) => e.action === "radar").length ?? 0), callback: () => { - setMode(0) + useGameProps.setState({ mode: 0 }) setTarget((e) => ({ ...e, show: false })) }, }, @@ -171,7 +166,7 @@ function EventBar({ clear }: { clear: () => void }) { amount: 1 - ships.filter((e) => e.size === 2).length, callback: () => { if (1 - ships.filter((e) => e.size === 2).length === 0) return - setMode(0) + useGameProps.setState({ mode: 0 }) }, }, { @@ -180,7 +175,7 @@ function EventBar({ clear }: { clear: () => void }) { amount: 3 - ships.filter((e) => e.size === 3).length, callback: () => { if (3 - ships.filter((e) => e.size === 3).length === 0) return - setMode(1) + useGameProps.setState({ mode: 1 }) }, }, { @@ -189,7 +184,17 @@ function EventBar({ clear }: { clear: () => void }) { amount: 2 - ships.filter((e) => e.size === 4).length, callback: () => { if (2 - ships.filter((e) => e.size === 4).length === 0) return - setMode(2) + useGameProps.setState({ mode: 2 }) + }, + }, + { + icon: faRotate, + text: "Rotate", + callback: () => { + setTargetPreview((t) => ({ + ...t, + orientation: t.orientation === "h" ? "v" : "h", + })) }, }, { @@ -263,9 +268,8 @@ function EventBar({ clear }: { clear: () => void }) { self?.moves, session?.user?.id, setIsReady, - setMenu, - setMode, setTarget, + setTargetPreview, ships, shouldHide, ] @@ -296,7 +300,7 @@ function EventBar({ clear }: { clear: () => void }) { text: "Return", iconColor: "#555", callback: () => { - setMenu("main") + useGameProps.setState({ menu: "main" }) }, }} > diff --git a/leaky-ships/components/Gamefield/Gamefield.tsx b/leaky-ships/components/Gamefield/Gamefield.tsx index 77638d9..612d77f 100644 --- a/leaky-ships/components/Gamefield/Gamefield.tsx +++ b/leaky-ships/components/Gamefield/Gamefield.tsx @@ -1,4 +1,3 @@ -import { MouseCursor } from "../../interfaces/frontend" // import Bluetooth from "./Bluetooth" // import FogImages from "./FogImages" import Labeling from "./Labeling" @@ -12,31 +11,20 @@ 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" +import { overlapsWithAnyBorder } from "@lib/utils/helpers" +import { CSSProperties } from "react" +import { useEffect } from "react" export const count = 12 function Gamefield() { - const [mouseCursor, setMouseCursor] = - useState(initlialMouseCursor) const { mode, - hits, target, targetPreview, + mouseCursor, ships, - addShip, payload, - DispatchAction, - setTarget, setTargetPreview, full, } = useGameProps() @@ -47,53 +35,36 @@ function Gamefield() { socket.emit("update", full) }, [full, payload?.game?.id, isConnected]) - const settingTarget = useCallback( - (isGameTile: boolean, x: number, y: number) => { - 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)) - } - }, - [ - 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)) - setTargetPreview((e) => ({ ...e, show: false })) + if ( + !shouldShow || + (payload?.game?.state === "running" && + overlapsWithAnyBorder(position, mode)) + ) + setTargetPreview((t) => ({ ...t, show: false })) else { - setTargetPreview({ + setTargetPreview((t) => ({ + ...t, ...position, show: !show || x !== position.x || y !== position.y, - }) + })) + if (payload?.game?.state !== "starting") return + const handleKeyPress = (event: KeyboardEvent) => { + if (event.key !== "r") return + setTargetPreview((t) => ({ + ...t, + orientation: t.orientation === "h" ? "v" : "h", + })) + } + document.addEventListener("keydown", handleKeyPress) + return () => { + document.removeEventListener("keydown", handleKeyPress) + } } - }, [mode, mouseCursor, setTargetPreview, target]) + }, [mode, mouseCursor, payload?.game?.state, setTargetPreview, target]) const { canvasRef, onMouseDown, clear } = useDraw() const { enable, color, shouldHide } = useDrawProps() @@ -104,19 +75,19 @@ function Gamefield() {
- setMouseCursor((e) => ({ ...e, shouldShow: false })) - } + // onMouseLeave={() => + // setMouseCursor((e) => ({ ...e, shouldShow: false })) + // } > {/* Bordes */} - + {/* Collumn lettes and row numbers */} - + {/* Ships */} - + {/* Fog images */} {/* */} diff --git a/leaky-ships/components/Gamefield/HitElems.tsx b/leaky-ships/components/Gamefield/HitElems.tsx index 12b81f6..8883ef7 100644 --- a/leaky-ships/components/Gamefield/HitElems.tsx +++ b/leaky-ships/components/Gamefield/HitElems.tsx @@ -1,18 +1,32 @@ 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 { CSSProperties } from "react" -function HitElems({ hits }: { hits: Hit[] }) { +function HitElems({ + props, +}: { + props?: { hits: Hit[]; colorOverride?: string } +}) { + const { hits } = useGameProps() + return ( <> - {hits.map(({ hit, x, y }, i) => ( + {(props?.hits ?? hits).map(({ hit, x, y }, i) => (
- +
))} diff --git a/leaky-ships/components/Gamefield/Labeling.tsx b/leaky-ships/components/Gamefield/Labeling.tsx index 5b33d21..2988f45 100644 --- a/leaky-ships/components/Gamefield/Labeling.tsx +++ b/leaky-ships/components/Gamefield/Labeling.tsx @@ -1,9 +1,10 @@ import { Field } from "../../interfaces/frontend" +import { count } from "./Gamefield" import { fieldIndex } from "@lib/utils/helpers" import classNames from "classnames" import { CSSProperties } from "react" -function Labeling({ count }: { count: number }) { +function Labeling() { let elems: (Field & { orientation: string })[] = [] diff --git a/leaky-ships/components/Gamefield/Ship.tsx b/leaky-ships/components/Gamefield/Ship.tsx index f85a0cd..144b08a 100644 --- a/leaky-ships/components/Gamefield/Ship.tsx +++ b/leaky-ships/components/Gamefield/Ship.tsx @@ -1,32 +1,60 @@ import { ShipProps } from "../../interfaces/frontend" -import { useGameProps } from "@hooks/useGameProps" import classNames from "classnames" -import React, { CSSProperties } from "react" +import React, { CSSProperties, useEffect, useRef } from "react" + +const sizes: { [n: number]: number } = { + 2: 96, + 3: 144, + 4: 196, +} function Ship({ - props: { size, variant, x, y }, + props: { size, variant, x, y, orientation }, preview, + warn, + color, }: { props: ShipProps preview?: boolean + warn?: boolean + color?: string }) { - const { payload, removeShip } = useGameProps() const filename = `ship_blue_${size}x_${variant}.gif` + const canvasRef = useRef(null) + + useEffect(() => { + const canvas = canvasRef.current + const ctx = canvas?.getContext("2d") + if (!canvas || !ctx) return + const gif = new Image() + gif.src = "/assets/" + filename + + // Load the GIF and start rendering + gif.onload = function () { + // Set the canvas size to match the GIF dimensions + canvas.width = orientation === "h" ? sizes[size] : 48 + canvas.height = orientation === "v" ? sizes[size] : 48 + + if (orientation === "v") + // Rotate the canvas by 90 degrees + ctx.rotate((90 * Math.PI) / 180) + + // Draw the rotated GIF + ctx.drawImage(gif, 0, orientation === "h" ? 0 : -48, sizes[size], 48) + } + }, [filename, orientation, size, x, y]) return (
{ - if (payload?.game?.state !== "starting") return - removeShip({ size, variant, x, y }) - useGameProps.setState({ mode: size - 2 }) - }} + style={ + { "--x": x, "--y": y, "--color": color ?? "limegreen" } as CSSProperties + } > - {filename} +
) } diff --git a/leaky-ships/components/Gamefield/Targets.tsx b/leaky-ships/components/Gamefield/Targets.tsx index 0199a75..f430ae8 100644 --- a/leaky-ships/components/Gamefield/Targets.tsx +++ b/leaky-ships/components/Gamefield/Targets.tsx @@ -1,7 +1,12 @@ import GamefieldPointer from "./GamefieldPointer" +import HitElems from "./HitElems" import Ship from "./Ship" import { useGameProps } from "@hooks/useGameProps" -import { composeTargetTiles, shipProps } from "@lib/utils/helpers" +import { + composeTargetTiles, + intersectingShip, + shipProps, +} from "@lib/utils/helpers" function Targets() { const { payload, target, targetPreview, mode, hits, ships } = useGameProps() @@ -20,8 +25,34 @@ function Targets() { ) - if (payload?.game?.state === "starting" && mode >= 0 && targetPreview.show) - return + if (payload?.game?.state === "starting" && mode >= 0 && targetPreview.show) { + const ship = shipProps(ships, mode, targetPreview) + const { fields, borders, score } = intersectingShip(ships, ship) + return ( + <> + 0} + color={fields.length ? "red" : borders.length ? "orange" : undefined} + key={targetPreview.orientation} + props={ship} + /> + ({ ...e, i, hit: true })), + }} + /> + ({ ...e, i, hit: true })), + colorOverride: "orange", + }} + /> + + ) + } + + return <> } export default Targets diff --git a/leaky-ships/hooks/useGameProps.ts b/leaky-ships/hooks/useGameProps.ts index b8fb22e..e27c4ba 100644 --- a/leaky-ships/hooks/useGameProps.ts +++ b/leaky-ships/hooks/useGameProps.ts @@ -2,13 +2,19 @@ import { ActionDispatchProps, EventBarModes, Hit, + MouseCursor, ShipProps, Target, + TargetPreview, } from "../interfaces/frontend" import { GameSettings } from "@components/Lobby/SettingsFrame/Setting" import { getPayloadwithChecksum } from "@lib/getPayloadwithChecksum" import { socket } from "@lib/socket" -import { initlialTarget, initlialTargetPreview } from "@lib/utils/helpers" +import { + initlialMouseCursor, + initlialTarget, + initlialTargetPreview, +} from "@lib/utils/helpers" import { GamePropsSchema, optionalGamePropsSchema, @@ -30,7 +36,8 @@ const initialState: optionalGamePropsSchema & { ships: ShipProps[] hits: Hit[] target: Target - targetPreview: Target + targetPreview: TargetPreview + mouseCursor: MouseCursor } = { menu: "actions", mode: 0, @@ -40,6 +47,7 @@ const initialState: optionalGamePropsSchema & { hits: [], target: initlialTarget, targetPreview: initlialTargetPreview, + mouseCursor: initlialMouseCursor, userStates: Array.from(Array(2), () => ({ isReady: false, isConnected: false, @@ -51,7 +59,8 @@ export type State = typeof initialState export type Action = { DispatchAction: (props: ActionDispatchProps) => void setTarget: (target: SetStateAction) => void - setTargetPreview: (targetPreview: SetStateAction) => void + setTargetPreview: (targetPreview: SetStateAction) => void + setMouseCursor: (mouseCursor: SetStateAction) => void setPlayer: (payload: { users: PlayerSchema[] }) => string | null setSetting: (settings: GameSettings) => string | null full: (newProps: GamePropsSchema) => void @@ -80,20 +89,28 @@ export const useGameProps = create()( // } }) ), - setTarget: (target) => + setTarget: (dispatch) => set( produce((state: State) => { - if (typeof target === "function") - state.target = target(state.target) - else state.target = target + if (typeof dispatch === "function") + state.target = dispatch(state.target) + else state.target = dispatch }) ), - setTargetPreview: (targetPreview) => + setTargetPreview: (dispatch) => set( produce((state: State) => { - if (typeof targetPreview === "function") - state.targetPreview = targetPreview(state.target) - else state.targetPreview = targetPreview + if (typeof dispatch === "function") + state.targetPreview = dispatch(state.targetPreview) + else state.targetPreview = dispatch + }) + ), + setMouseCursor: (dispatch) => + set( + produce((state: State) => { + if (typeof dispatch === "function") + state.mouseCursor = dispatch(state.mouseCursor) + else state.mouseCursor = dispatch }) ), addShip: (props) => diff --git a/leaky-ships/interfaces/frontend.ts b/leaky-ships/interfaces/frontend.ts index bb3cb05..65413ba 100644 --- a/leaky-ships/interfaces/frontend.ts +++ b/leaky-ships/interfaces/frontend.ts @@ -1,5 +1,5 @@ import { IconDefinition } from "@fortawesome/pro-solid-svg-icons" -import { MoveType } from "@prisma/client" +import { MoveType, Orientation } from "@prisma/client" export interface Position { x: number @@ -8,6 +8,9 @@ export interface Position { export interface Target extends Position { show: boolean } +export interface TargetPreview extends Target { + orientation: Orientation +} export interface MouseCursor extends Position { shouldShow: boolean } @@ -40,24 +43,23 @@ export interface Field extends Position { export interface Hit extends Position { hit: boolean } - export interface Point extends Position {} - export interface DrawLineProps { currentPoint: Point prevPoint: Point | null color: string } - export interface Draw extends DrawLineProps { ctx: CanvasRenderingContext2D } - export interface ShipProps extends Position { size: number variant: number + orientation: Orientation +} +export interface IndexedPosition extends Position { + i?: number } - export interface ActionDispatchProps extends Position { index?: number action: MoveType diff --git a/leaky-ships/lib/utils/helpers.ts b/leaky-ships/lib/utils/helpers.ts index 5ab5466..dec9aa1 100644 --- a/leaky-ships/lib/utils/helpers.ts +++ b/leaky-ships/lib/utils/helpers.ts @@ -1,14 +1,16 @@ import type { Hit, + IndexedPosition, Mode, Position, ShipProps, Target, TargetList, + TargetPreview, } from "../../interfaces/frontend" import { count } from "@components/Gamefield/Gamefield" import { PointerProps } from "@components/Gamefield/GamefieldPointer" -import { useGameProps } from "@hooks/useGameProps" +import { Orientation } from "@prisma/client" export function borderCN(count: number, x: number, y: number) { if (x === 0) return "left" @@ -47,7 +49,7 @@ const modes: Mode[] = [ }, ] -function isBorder(x: number, y: number, count: number) { +function isBorder(x: number, y: number) { return x < 2 || x > count + 1 || y < 2 || y > count + 1 } @@ -86,8 +88,7 @@ export function targetList( } export function overlapsWithAnyBorder(target: Position, mode: number) { - return !!targetList(target, mode).filter(({ x, y }) => isBorder(x, y, count)) - .length + return !!targetList(target, mode).filter(({ x, y }) => isBorder(x, y)).length } export function composeTargetTiles( @@ -107,16 +108,15 @@ export function composeTargetTiles( } export const initlialTarget = { - preview: false, - show: false, x: 2, y: 2, + show: false, } export const initlialTargetPreview = { - preview: true, - show: false, x: 2, y: 2, + show: false, + orientation: Orientation.h, } export const initlialMouseCursor = { shouldShow: false, @@ -127,7 +127,7 @@ export const initlialMouseCursor = { export const shipProps = ( ships: ShipProps[], mode: number, - targetPreview: Target + targetPreview: Position & { orientation: Orientation } ) => ({ size: mode + 2, variant: @@ -135,9 +135,83 @@ export const shipProps = ( .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, + x: + targetPreview.orientation === "h" + ? targetPreview.x - Math.floor((mode + 2) / 2) + : targetPreview.x, + y: + targetPreview.orientation === "v" + ? targetPreview.y - Math.floor((mode + 2) / 2) + : targetPreview.y, + orientation: targetPreview.orientation, }) +export function shipFields(ship: ShipProps, i?: number) { + let fields: IndexedPosition[] = [] + let borders: IndexedPosition[] = [] + for ( + let x = ship.x; + x <= (ship.orientation === "h" ? ship.x + ship.size - 1 : ship.x); + x++ + ) { + for ( + let y = ship.y; + y <= (ship.orientation === "v" ? ship.y + ship.size - 1 : ship.y); + y++ + ) { + fields.push({ x, y, i }) + } + } + + for ( + let x = ship.x - 1; + x <= (ship.orientation === "h" ? ship.x + ship.size : ship.x + 1); + x++ + ) { + for ( + let y = ship.y - 1; + y <= (ship.orientation === "v" ? ship.y + ship.size : ship.y + 1); + y++ + ) { + if (isBorder(x, y) || fields.filter((e) => e.x === x && e.y === y).length) + continue + borders.push({ x, y, i }) + } + } + return { fields, borders } +} + +export function intersectingShip( + ships: ShipProps[], + ship: ShipProps, + withBorder?: boolean +) { + const thisShip = shipFields(ship) + const reducedShips = ships.reduce( + (prev, curr, i) => { + const { fields, borders } = shipFields(curr, i) + return { + fields: [...prev.fields, ...fields], + borders: [...prev.borders, ...borders], + } + }, + { fields: [] as IndexedPosition[], borders: [] as IndexedPosition[] } + ) + const fields = reducedShips.fields.filter( + (e) => thisShip.fields.filter((e2) => e2.x === e.x && e2.y === e.y).length + ) + const borders = thisShip.fields.filter( + (e) => + reducedShips.borders.filter((e2) => e2.x === e.x && e2.y === e.y).length + ) + const isInBorder = thisShip.fields.filter( + (e) => withBorder && isBorder(e.x, e.y) + ) + return { + score: isInBorder.length ? 2 : fields.length || borders.length ? 1 : 0, + index: fields[0]?.i, + fields, + borders, + } +} diff --git a/leaky-ships/lib/zodSchemas.ts b/leaky-ships/lib/zodSchemas.ts index b7b854b..b998ff9 100644 --- a/leaky-ships/lib/zodSchemas.ts +++ b/leaky-ships/lib/zodSchemas.ts @@ -1,5 +1,4 @@ -import { ShipSchema } from "../prisma/generated/zod" -import { GameState } from "@prisma/client" +import { GameState, MoveType, Orientation } from "@prisma/client" import { z } from "zod" export const PlayerSchema = z @@ -19,7 +18,7 @@ export const PlayerSchema = z .object({ id: z.string(), index: z.number(), - action: z.string(), + action: z.nativeEnum(MoveType), x: z.number(), y: z.number(), }) @@ -45,6 +44,7 @@ export const CreateSchema = z.object({ variant: z.number(), x: z.number(), y: z.number(), + orientation: z.nativeEnum(Orientation), }) .array(), }) diff --git a/leaky-ships/pages/api/game/running.ts b/leaky-ships/pages/api/game/running.ts index eaefae5..5247d38 100644 --- a/leaky-ships/pages/api/game/running.ts +++ b/leaky-ships/pages/api/game/running.ts @@ -22,6 +22,7 @@ export const gameSelects = { variant: true, x: true, y: true, + orientation: true, }, }, gamePin: { @@ -48,6 +49,7 @@ export const gameSelects = { action: true, x: true, y: true, + orientation: true, }, }, user: { diff --git a/leaky-ships/prisma/generated/zod/index.ts b/leaky-ships/prisma/generated/zod/index.ts index 08edf7b..de69867 100644 --- a/leaky-ships/prisma/generated/zod/index.ts +++ b/leaky-ships/prisma/generated/zod/index.ts @@ -18,11 +18,11 @@ export const GameScalarFieldEnumSchema = z.enum(['id','createdAt','updatedAt','s export const GamepinScalarFieldEnumSchema = z.enum(['id','createdAt','pin','gameId']); -export const MoveScalarFieldEnumSchema = z.enum(['id','createdAt','index','action','x','y','user_game_id']); +export const MoveScalarFieldEnumSchema = z.enum(['id','createdAt','index','action','x','y','orientation','user_game_id']); export const SessionScalarFieldEnumSchema = z.enum(['id','sessionToken','userId','expires']); -export const ShipScalarFieldEnumSchema = z.enum(['id','size','variant','x','y','gameId']); +export const ShipScalarFieldEnumSchema = z.enum(['id','size','variant','x','y','orientation','gameId']); export const SortOrderSchema = z.enum(['asc','desc']); @@ -34,6 +34,10 @@ export const User_GameScalarFieldEnumSchema = z.enum(['id','createdAt','gameId', export const VerificationTokenScalarFieldEnumSchema = z.enum(['identifier','token','expires']); +export const OrientationSchema = z.enum(['h','v']); + +export type OrientationType = `${z.infer}` + export const GameStateSchema = z.enum(['lobby','starting','running','ended']); export type GameStateType = `${z.infer}` @@ -116,6 +120,7 @@ export type VerificationToken = z.infer ///////////////////////////////////////// export const ShipSchema = z.object({ + orientation: OrientationSchema, id: z.string().cuid(), size: z.number().int(), variant: z.number().int(), @@ -176,6 +181,7 @@ export type User_Game = z.infer export const MoveSchema = z.object({ action: MoveTypeSchema, + orientation: OrientationSchema, id: z.string().cuid(), createdAt: z.coerce.date(), index: z.number().int(), @@ -321,6 +327,7 @@ export const ShipSelectSchema: z.ZodType = z.object({ variant: z.boolean().optional(), x: z.boolean().optional(), y: z.boolean().optional(), + orientation: z.boolean().optional(), gameId: z.boolean().optional(), game: z.union([z.boolean(),z.lazy(() => GameArgsSchema)]).optional(), }).strict() @@ -441,6 +448,7 @@ export const MoveSelectSchema: z.ZodType = z.object({ action: z.boolean().optional(), x: z.boolean().optional(), y: z.boolean().optional(), + orientation: z.boolean().optional(), user_game_id: z.boolean().optional(), user_game: z.union([z.boolean(),z.lazy(() => User_GameArgsSchema)]).optional(), }).strict() @@ -712,6 +720,7 @@ export const ShipWhereInputSchema: z.ZodType = z.object({ variant: z.union([ z.lazy(() => IntFilterSchema),z.number() ]).optional(), x: z.union([ z.lazy(() => IntFilterSchema),z.number() ]).optional(), y: z.union([ z.lazy(() => IntFilterSchema),z.number() ]).optional(), + orientation: z.union([ z.lazy(() => EnumOrientationFilterSchema),z.lazy(() => OrientationSchema) ]).optional(), gameId: z.union([ z.lazy(() => StringFilterSchema),z.string() ]).optional(), game: z.union([ z.lazy(() => GameRelationFilterSchema),z.lazy(() => GameWhereInputSchema) ]).optional(), }).strict(); @@ -722,6 +731,7 @@ export const ShipOrderByWithRelationInputSchema: z.ZodType SortOrderSchema).optional(), x: z.lazy(() => SortOrderSchema).optional(), y: z.lazy(() => SortOrderSchema).optional(), + orientation: z.lazy(() => SortOrderSchema).optional(), gameId: z.lazy(() => SortOrderSchema).optional(), game: z.lazy(() => GameOrderByWithRelationInputSchema).optional() }).strict(); @@ -736,6 +746,7 @@ export const ShipOrderByWithAggregationInputSchema: z.ZodType SortOrderSchema).optional(), x: z.lazy(() => SortOrderSchema).optional(), y: z.lazy(() => SortOrderSchema).optional(), + orientation: z.lazy(() => SortOrderSchema).optional(), gameId: z.lazy(() => SortOrderSchema).optional(), _count: z.lazy(() => ShipCountOrderByAggregateInputSchema).optional(), _avg: z.lazy(() => ShipAvgOrderByAggregateInputSchema).optional(), @@ -753,6 +764,7 @@ export const ShipScalarWhereWithAggregatesInputSchema: z.ZodType IntWithAggregatesFilterSchema),z.number() ]).optional(), x: z.union([ z.lazy(() => IntWithAggregatesFilterSchema),z.number() ]).optional(), y: z.union([ z.lazy(() => IntWithAggregatesFilterSchema),z.number() ]).optional(), + orientation: z.union([ z.lazy(() => EnumOrientationWithAggregatesFilterSchema),z.lazy(() => OrientationSchema) ]).optional(), gameId: z.union([ z.lazy(() => StringWithAggregatesFilterSchema),z.string() ]).optional(), }).strict(); @@ -931,6 +943,7 @@ export const MoveWhereInputSchema: z.ZodType = z.object({ action: z.union([ z.lazy(() => EnumMoveTypeFilterSchema),z.lazy(() => MoveTypeSchema) ]).optional(), x: z.union([ z.lazy(() => IntFilterSchema),z.number() ]).optional(), y: z.union([ z.lazy(() => IntFilterSchema),z.number() ]).optional(), + orientation: z.union([ z.lazy(() => EnumOrientationFilterSchema),z.lazy(() => OrientationSchema) ]).optional(), user_game_id: z.union([ z.lazy(() => StringFilterSchema),z.string() ]).optional(), user_game: z.union([ z.lazy(() => User_GameRelationFilterSchema),z.lazy(() => User_GameWhereInputSchema) ]).optional(), }).strict(); @@ -942,6 +955,7 @@ export const MoveOrderByWithRelationInputSchema: z.ZodType SortOrderSchema).optional(), x: z.lazy(() => SortOrderSchema).optional(), y: z.lazy(() => SortOrderSchema).optional(), + orientation: z.lazy(() => SortOrderSchema).optional(), user_game_id: z.lazy(() => SortOrderSchema).optional(), user_game: z.lazy(() => User_GameOrderByWithRelationInputSchema).optional() }).strict(); @@ -959,6 +973,7 @@ export const MoveOrderByWithAggregationInputSchema: z.ZodType SortOrderSchema).optional(), x: z.lazy(() => SortOrderSchema).optional(), y: z.lazy(() => SortOrderSchema).optional(), + orientation: z.lazy(() => SortOrderSchema).optional(), user_game_id: z.lazy(() => SortOrderSchema).optional(), _count: z.lazy(() => MoveCountOrderByAggregateInputSchema).optional(), _avg: z.lazy(() => MoveAvgOrderByAggregateInputSchema).optional(), @@ -977,6 +992,7 @@ export const MoveScalarWhereWithAggregatesInputSchema: z.ZodType EnumMoveTypeWithAggregatesFilterSchema),z.lazy(() => MoveTypeSchema) ]).optional(), x: z.union([ z.lazy(() => IntWithAggregatesFilterSchema),z.number() ]).optional(), y: z.union([ z.lazy(() => IntWithAggregatesFilterSchema),z.number() ]).optional(), + orientation: z.union([ z.lazy(() => EnumOrientationWithAggregatesFilterSchema),z.lazy(() => OrientationSchema) ]).optional(), user_game_id: z.union([ z.lazy(() => StringWithAggregatesFilterSchema),z.string() ]).optional(), }).strict(); @@ -1330,6 +1346,7 @@ export const ShipCreateInputSchema: z.ZodType = z.object variant: z.number().int(), x: z.number().int(), y: z.number().int(), + orientation: z.lazy(() => OrientationSchema), game: z.lazy(() => GameCreateNestedOneWithoutShipsInputSchema) }).strict(); @@ -1339,6 +1356,7 @@ export const ShipUncheckedCreateInputSchema: z.ZodType OrientationSchema), gameId: z.string() }).strict(); @@ -1348,6 +1366,7 @@ export const ShipUpdateInputSchema: z.ZodType = z.object variant: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), x: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), y: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), + orientation: z.union([ z.lazy(() => OrientationSchema),z.lazy(() => EnumOrientationFieldUpdateOperationsInputSchema) ]).optional(), game: z.lazy(() => GameUpdateOneRequiredWithoutShipsNestedInputSchema).optional() }).strict(); @@ -1357,6 +1376,7 @@ export const ShipUncheckedUpdateInputSchema: z.ZodType IntFieldUpdateOperationsInputSchema) ]).optional(), x: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), y: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), + orientation: z.union([ z.lazy(() => OrientationSchema),z.lazy(() => EnumOrientationFieldUpdateOperationsInputSchema) ]).optional(), gameId: z.union([ z.string(),z.lazy(() => StringFieldUpdateOperationsInputSchema) ]).optional(), }).strict(); @@ -1366,6 +1386,7 @@ export const ShipCreateManyInputSchema: z.ZodType = variant: z.number().int(), x: z.number().int(), y: z.number().int(), + orientation: z.lazy(() => OrientationSchema), gameId: z.string() }).strict(); @@ -1375,6 +1396,7 @@ export const ShipUpdateManyMutationInputSchema: z.ZodType IntFieldUpdateOperationsInputSchema) ]).optional(), x: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), y: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), + orientation: z.union([ z.lazy(() => OrientationSchema),z.lazy(() => EnumOrientationFieldUpdateOperationsInputSchema) ]).optional(), }).strict(); export const ShipUncheckedUpdateManyInputSchema: z.ZodType = z.object({ @@ -1383,6 +1405,7 @@ export const ShipUncheckedUpdateManyInputSchema: z.ZodType IntFieldUpdateOperationsInputSchema) ]).optional(), x: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), y: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), + orientation: z.union([ z.lazy(() => OrientationSchema),z.lazy(() => EnumOrientationFieldUpdateOperationsInputSchema) ]).optional(), gameId: z.union([ z.string(),z.lazy(() => StringFieldUpdateOperationsInputSchema) ]).optional(), }).strict(); @@ -1592,6 +1615,7 @@ export const MoveCreateInputSchema: z.ZodType = z.object action: z.lazy(() => MoveTypeSchema), x: z.number().int(), y: z.number().int(), + orientation: z.lazy(() => OrientationSchema), user_game: z.lazy(() => User_GameCreateNestedOneWithoutMovesInputSchema) }).strict(); @@ -1602,6 +1626,7 @@ export const MoveUncheckedCreateInputSchema: z.ZodType MoveTypeSchema), x: z.number().int(), y: z.number().int(), + orientation: z.lazy(() => OrientationSchema), user_game_id: z.string() }).strict(); @@ -1612,6 +1637,7 @@ export const MoveUpdateInputSchema: z.ZodType = z.object action: z.union([ z.lazy(() => MoveTypeSchema),z.lazy(() => EnumMoveTypeFieldUpdateOperationsInputSchema) ]).optional(), x: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), y: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), + orientation: z.union([ z.lazy(() => OrientationSchema),z.lazy(() => EnumOrientationFieldUpdateOperationsInputSchema) ]).optional(), user_game: z.lazy(() => User_GameUpdateOneRequiredWithoutMovesNestedInputSchema).optional() }).strict(); @@ -1622,6 +1648,7 @@ export const MoveUncheckedUpdateInputSchema: z.ZodType MoveTypeSchema),z.lazy(() => EnumMoveTypeFieldUpdateOperationsInputSchema) ]).optional(), x: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), y: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), + orientation: z.union([ z.lazy(() => OrientationSchema),z.lazy(() => EnumOrientationFieldUpdateOperationsInputSchema) ]).optional(), user_game_id: z.union([ z.string(),z.lazy(() => StringFieldUpdateOperationsInputSchema) ]).optional(), }).strict(); @@ -1632,6 +1659,7 @@ export const MoveCreateManyInputSchema: z.ZodType = action: z.lazy(() => MoveTypeSchema), x: z.number().int(), y: z.number().int(), + orientation: z.lazy(() => OrientationSchema), user_game_id: z.string() }).strict(); @@ -1642,6 +1670,7 @@ export const MoveUpdateManyMutationInputSchema: z.ZodType MoveTypeSchema),z.lazy(() => EnumMoveTypeFieldUpdateOperationsInputSchema) ]).optional(), x: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), y: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), + orientation: z.union([ z.lazy(() => OrientationSchema),z.lazy(() => EnumOrientationFieldUpdateOperationsInputSchema) ]).optional(), }).strict(); export const MoveUncheckedUpdateManyInputSchema: z.ZodType = z.object({ @@ -1651,6 +1680,7 @@ export const MoveUncheckedUpdateManyInputSchema: z.ZodType MoveTypeSchema),z.lazy(() => EnumMoveTypeFieldUpdateOperationsInputSchema) ]).optional(), x: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), y: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), + orientation: z.union([ z.lazy(() => OrientationSchema),z.lazy(() => EnumOrientationFieldUpdateOperationsInputSchema) ]).optional(), user_game_id: z.union([ z.string(),z.lazy(() => StringFieldUpdateOperationsInputSchema) ]).optional(), }).strict(); @@ -2037,6 +2067,13 @@ export const IntFilterSchema: z.ZodType = z.object({ not: z.union([ z.number(),z.lazy(() => NestedIntFilterSchema) ]).optional(), }).strict(); +export const EnumOrientationFilterSchema: z.ZodType = z.object({ + equals: z.lazy(() => OrientationSchema).optional(), + in: z.union([ z.lazy(() => OrientationSchema).array(),z.lazy(() => OrientationSchema) ]).optional(), + notIn: z.union([ z.lazy(() => OrientationSchema).array(),z.lazy(() => OrientationSchema) ]).optional(), + not: z.union([ z.lazy(() => OrientationSchema),z.lazy(() => NestedEnumOrientationFilterSchema) ]).optional(), +}).strict(); + export const GameRelationFilterSchema: z.ZodType = z.object({ is: z.lazy(() => GameWhereInputSchema).optional(), isNot: z.lazy(() => GameWhereInputSchema).optional() @@ -2048,6 +2085,7 @@ export const ShipCountOrderByAggregateInputSchema: z.ZodType SortOrderSchema).optional(), x: z.lazy(() => SortOrderSchema).optional(), y: z.lazy(() => SortOrderSchema).optional(), + orientation: z.lazy(() => SortOrderSchema).optional(), gameId: z.lazy(() => SortOrderSchema).optional() }).strict(); @@ -2064,6 +2102,7 @@ export const ShipMaxOrderByAggregateInputSchema: z.ZodType SortOrderSchema).optional(), x: z.lazy(() => SortOrderSchema).optional(), y: z.lazy(() => SortOrderSchema).optional(), + orientation: z.lazy(() => SortOrderSchema).optional(), gameId: z.lazy(() => SortOrderSchema).optional() }).strict(); @@ -2073,6 +2112,7 @@ export const ShipMinOrderByAggregateInputSchema: z.ZodType SortOrderSchema).optional(), x: z.lazy(() => SortOrderSchema).optional(), y: z.lazy(() => SortOrderSchema).optional(), + orientation: z.lazy(() => SortOrderSchema).optional(), gameId: z.lazy(() => SortOrderSchema).optional() }).strict(); @@ -2099,6 +2139,16 @@ export const IntWithAggregatesFilterSchema: z.ZodType NestedIntFilterSchema).optional() }).strict(); +export const EnumOrientationWithAggregatesFilterSchema: z.ZodType = z.object({ + equals: z.lazy(() => OrientationSchema).optional(), + in: z.union([ z.lazy(() => OrientationSchema).array(),z.lazy(() => OrientationSchema) ]).optional(), + notIn: z.union([ z.lazy(() => OrientationSchema).array(),z.lazy(() => OrientationSchema) ]).optional(), + not: z.union([ z.lazy(() => OrientationSchema),z.lazy(() => NestedEnumOrientationWithAggregatesFilterSchema) ]).optional(), + _count: z.lazy(() => NestedIntFilterSchema).optional(), + _min: z.lazy(() => NestedEnumOrientationFilterSchema).optional(), + _max: z.lazy(() => NestedEnumOrientationFilterSchema).optional() +}).strict(); + export const EnumGameStateFilterSchema: z.ZodType = z.object({ equals: z.lazy(() => GameStateSchema).optional(), in: z.union([ z.lazy(() => GameStateSchema).array(),z.lazy(() => GameStateSchema) ]).optional(), @@ -2290,6 +2340,7 @@ export const MoveCountOrderByAggregateInputSchema: z.ZodType SortOrderSchema).optional(), x: z.lazy(() => SortOrderSchema).optional(), y: z.lazy(() => SortOrderSchema).optional(), + orientation: z.lazy(() => SortOrderSchema).optional(), user_game_id: z.lazy(() => SortOrderSchema).optional() }).strict(); @@ -2306,6 +2357,7 @@ export const MoveMaxOrderByAggregateInputSchema: z.ZodType SortOrderSchema).optional(), x: z.lazy(() => SortOrderSchema).optional(), y: z.lazy(() => SortOrderSchema).optional(), + orientation: z.lazy(() => SortOrderSchema).optional(), user_game_id: z.lazy(() => SortOrderSchema).optional() }).strict(); @@ -2316,6 +2368,7 @@ export const MoveMinOrderByAggregateInputSchema: z.ZodType SortOrderSchema).optional(), x: z.lazy(() => SortOrderSchema).optional(), y: z.lazy(() => SortOrderSchema).optional(), + orientation: z.lazy(() => SortOrderSchema).optional(), user_game_id: z.lazy(() => SortOrderSchema).optional() }).strict(); @@ -2551,6 +2604,10 @@ export const IntFieldUpdateOperationsInputSchema: z.ZodType = z.object({ + set: z.lazy(() => OrientationSchema).optional() +}).strict(); + export const GameUpdateOneRequiredWithoutShipsNestedInputSchema: z.ZodType = z.object({ create: z.union([ z.lazy(() => GameCreateWithoutShipsInputSchema),z.lazy(() => GameUncheckedCreateWithoutShipsInputSchema) ]).optional(), connectOrCreate: z.lazy(() => GameCreateOrConnectWithoutShipsInputSchema).optional(), @@ -3002,6 +3059,13 @@ export const NestedDateTimeNullableWithAggregatesFilterSchema: z.ZodType NestedDateTimeNullableFilterSchema).optional() }).strict(); +export const NestedEnumOrientationFilterSchema: z.ZodType = z.object({ + equals: z.lazy(() => OrientationSchema).optional(), + in: z.union([ z.lazy(() => OrientationSchema).array(),z.lazy(() => OrientationSchema) ]).optional(), + notIn: z.union([ z.lazy(() => OrientationSchema).array(),z.lazy(() => OrientationSchema) ]).optional(), + not: z.union([ z.lazy(() => OrientationSchema),z.lazy(() => NestedEnumOrientationFilterSchema) ]).optional(), +}).strict(); + export const NestedIntWithAggregatesFilterSchema: z.ZodType = z.object({ equals: z.number().optional(), in: z.union([ z.number().array(),z.number() ]).optional(), @@ -3029,6 +3093,16 @@ export const NestedFloatFilterSchema: z.ZodType = z.ob not: z.union([ z.number(),z.lazy(() => NestedFloatFilterSchema) ]).optional(), }).strict(); +export const NestedEnumOrientationWithAggregatesFilterSchema: z.ZodType = z.object({ + equals: z.lazy(() => OrientationSchema).optional(), + in: z.union([ z.lazy(() => OrientationSchema).array(),z.lazy(() => OrientationSchema) ]).optional(), + notIn: z.union([ z.lazy(() => OrientationSchema).array(),z.lazy(() => OrientationSchema) ]).optional(), + not: z.union([ z.lazy(() => OrientationSchema),z.lazy(() => NestedEnumOrientationWithAggregatesFilterSchema) ]).optional(), + _count: z.lazy(() => NestedIntFilterSchema).optional(), + _min: z.lazy(() => NestedEnumOrientationFilterSchema).optional(), + _max: z.lazy(() => NestedEnumOrientationFilterSchema).optional() +}).strict(); + export const NestedEnumGameStateFilterSchema: z.ZodType = z.object({ equals: z.lazy(() => GameStateSchema).optional(), in: z.union([ z.lazy(() => GameStateSchema).array(),z.lazy(() => GameStateSchema) ]).optional(), @@ -3443,7 +3517,8 @@ export const ShipCreateWithoutGameInputSchema: z.ZodType OrientationSchema) }).strict(); export const ShipUncheckedCreateWithoutGameInputSchema: z.ZodType = z.object({ @@ -3451,7 +3526,8 @@ export const ShipUncheckedCreateWithoutGameInputSchema: z.ZodType OrientationSchema) }).strict(); export const ShipCreateOrConnectWithoutGameInputSchema: z.ZodType = z.object({ @@ -3534,6 +3610,7 @@ export const ShipScalarWhereInputSchema: z.ZodType variant: z.union([ z.lazy(() => IntFilterSchema),z.number() ]).optional(), x: z.union([ z.lazy(() => IntFilterSchema),z.number() ]).optional(), y: z.union([ z.lazy(() => IntFilterSchema),z.number() ]).optional(), + orientation: z.union([ z.lazy(() => EnumOrientationFilterSchema),z.lazy(() => OrientationSchema) ]).optional(), gameId: z.union([ z.lazy(() => StringFilterSchema),z.string() ]).optional(), }).strict(); @@ -3638,7 +3715,8 @@ export const MoveCreateWithoutUser_gameInputSchema: z.ZodType MoveTypeSchema), x: z.number().int(), - y: z.number().int() + y: z.number().int(), + orientation: z.lazy(() => OrientationSchema) }).strict(); export const MoveUncheckedCreateWithoutUser_gameInputSchema: z.ZodType = z.object({ @@ -3647,7 +3725,8 @@ export const MoveUncheckedCreateWithoutUser_gameInputSchema: z.ZodType MoveTypeSchema), x: z.number().int(), - y: z.number().int() + y: z.number().int(), + orientation: z.lazy(() => OrientationSchema) }).strict(); export const MoveCreateOrConnectWithoutUser_gameInputSchema: z.ZodType = z.object({ @@ -3770,6 +3849,7 @@ export const MoveScalarWhereInputSchema: z.ZodType action: z.union([ z.lazy(() => EnumMoveTypeFilterSchema),z.lazy(() => MoveTypeSchema) ]).optional(), x: z.union([ z.lazy(() => IntFilterSchema),z.number() ]).optional(), y: z.union([ z.lazy(() => IntFilterSchema),z.number() ]).optional(), + orientation: z.union([ z.lazy(() => EnumOrientationFilterSchema),z.lazy(() => OrientationSchema) ]).optional(), user_game_id: z.union([ z.lazy(() => StringFilterSchema),z.string() ]).optional(), }).strict(); @@ -4081,7 +4161,8 @@ export const ShipCreateManyGameInputSchema: z.ZodType OrientationSchema) }).strict(); export const User_GameCreateManyGameInputSchema: z.ZodType = z.object({ @@ -4097,6 +4178,7 @@ export const ShipUpdateWithoutGameInputSchema: z.ZodType IntFieldUpdateOperationsInputSchema) ]).optional(), x: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), y: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), + orientation: z.union([ z.lazy(() => OrientationSchema),z.lazy(() => EnumOrientationFieldUpdateOperationsInputSchema) ]).optional(), }).strict(); export const ShipUncheckedUpdateWithoutGameInputSchema: z.ZodType = z.object({ @@ -4105,6 +4187,7 @@ export const ShipUncheckedUpdateWithoutGameInputSchema: z.ZodType IntFieldUpdateOperationsInputSchema) ]).optional(), x: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), y: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), + orientation: z.union([ z.lazy(() => OrientationSchema),z.lazy(() => EnumOrientationFieldUpdateOperationsInputSchema) ]).optional(), }).strict(); export const ShipUncheckedUpdateManyWithoutShipsInputSchema: z.ZodType = z.object({ @@ -4113,6 +4196,7 @@ export const ShipUncheckedUpdateManyWithoutShipsInputSchema: z.ZodType IntFieldUpdateOperationsInputSchema) ]).optional(), x: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), y: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), + orientation: z.union([ z.lazy(() => OrientationSchema),z.lazy(() => EnumOrientationFieldUpdateOperationsInputSchema) ]).optional(), }).strict(); export const User_GameUpdateWithoutGameInputSchema: z.ZodType = z.object({ @@ -4146,7 +4230,8 @@ export const MoveCreateManyUser_gameInputSchema: z.ZodType MoveTypeSchema), x: z.number().int(), - y: z.number().int() + y: z.number().int(), + orientation: z.lazy(() => OrientationSchema) }).strict(); export const ChatCreateManyUser_gameInputSchema: z.ZodType = z.object({ @@ -4163,6 +4248,7 @@ export const MoveUpdateWithoutUser_gameInputSchema: z.ZodType MoveTypeSchema),z.lazy(() => EnumMoveTypeFieldUpdateOperationsInputSchema) ]).optional(), x: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), y: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), + orientation: z.union([ z.lazy(() => OrientationSchema),z.lazy(() => EnumOrientationFieldUpdateOperationsInputSchema) ]).optional(), }).strict(); export const MoveUncheckedUpdateWithoutUser_gameInputSchema: z.ZodType = z.object({ @@ -4172,6 +4258,7 @@ export const MoveUncheckedUpdateWithoutUser_gameInputSchema: z.ZodType MoveTypeSchema),z.lazy(() => EnumMoveTypeFieldUpdateOperationsInputSchema) ]).optional(), x: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), y: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), + orientation: z.union([ z.lazy(() => OrientationSchema),z.lazy(() => EnumOrientationFieldUpdateOperationsInputSchema) ]).optional(), }).strict(); export const MoveUncheckedUpdateManyWithoutMovesInputSchema: z.ZodType = z.object({ @@ -4181,6 +4268,7 @@ export const MoveUncheckedUpdateManyWithoutMovesInputSchema: z.ZodType MoveTypeSchema),z.lazy(() => EnumMoveTypeFieldUpdateOperationsInputSchema) ]).optional(), x: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), y: z.union([ z.number().int(),z.lazy(() => IntFieldUpdateOperationsInputSchema) ]).optional(), + orientation: z.union([ z.lazy(() => OrientationSchema),z.lazy(() => EnumOrientationFieldUpdateOperationsInputSchema) ]).optional(), }).strict(); export const ChatUpdateWithoutUser_gameInputSchema: z.ZodType = z.object({ diff --git a/leaky-ships/prisma/schema.prisma b/leaky-ships/prisma/schema.prisma index ae218f5..8f947b4 100644 --- a/leaky-ships/prisma/schema.prisma +++ b/leaky-ships/prisma/schema.prisma @@ -68,6 +68,22 @@ 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 + gameId String + game Game @relation(fields: [gameId], references: [id], onDelete: Cascade) +} + enum GameState { lobby starting @@ -75,16 +91,6 @@ 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()) @@ -130,14 +136,15 @@ enum MoveType { } model Move { - id String @id @default(cuid()) - createdAt DateTime @default(now()) + id String @id @default(cuid()) + createdAt DateTime @default(now()) index Int action MoveType x Int y Int + orientation Orientation 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([action, x, y]) diff --git a/leaky-ships/styles/App.scss b/leaky-ships/styles/App.scss index 0371281..361639c 100644 --- a/leaky-ships/styles/App.scss +++ b/leaky-ships/styles/App.scss @@ -76,6 +76,8 @@ body { > .label { grid-column: var(--x); grid-row: var(--y); + font-size: initial; + transform: scale(1.8); } > .border-tile { @@ -108,42 +110,63 @@ body { position: relative; @include flex-col; align-items: center; - grid-row: var(--y); + justify-content: center; pointer-events: none; + transform-origin: 24px 50%; - img { + canvas { @include pixelart; position: absolute; - // height: 90%; - width: 90%; - // 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; + outline: 3px dashed var(--color); + + &.warn { + animation: blink 0.5s ease-in-out alternate-reverse infinite; + @keyframes blink { + from { + opacity: 0.2; + } } } } + &.h { + grid-row: var(--y); + canvas { + width: 90%; + } - &.s2 { - grid-column: var(--x) / calc(var(--x) + 2); + &.s2 { + grid-column: var(--x) / calc(var(--x) + 2); + } + + &.s3 { + grid-column: var(--x) / calc(var(--x) + 3); + } + + &.s4 { + grid-column: var(--x) / calc(var(--x) + 4); + } } + &.v { + grid-column: var(--x); + canvas { + height: 90%; + } - &.s3 { - grid-column: var(--x) / calc(var(--x) + 3); - } + &.s2 { + grid-row: var(--y) / calc(var(--y) + 2); + } - &.s4 { - grid-column: var(--x) / calc(var(--x) + 4); + &.s3 { + grid-row: var(--y) / calc(var(--y) + 3); + } + + &.s4 { + grid-row: var(--y) / calc(var(--y) + 4); + } } } @@ -170,10 +193,6 @@ body { box-sizing: border-box; padding: 25%; - - &.fa-burst { - color: red; - } } &.target {