From 7432542126c6e71acc3e6a0deb8617d610ca8c24 Mon Sep 17 00:00:00 2001 From: ymmot239 <77180103+ymmot239@users.noreply.github.com> Date: Fri, 15 Nov 2024 18:56:33 -0600 Subject: [PATCH 01/15] Created Priority queue Added a priority queue to the api Added a sidebar to the setup page to show when people are in line. Allows for people to set their own names and kicks them out when they disconnect. Need to: Work on host switching Add queue to the in-progress game page. --- src/client/index.scss | 20 +++++++++ src/client/setup/queue.tsx | 65 +++++++++++++++++++++++++++++ src/client/setup/setup-base.tsx | 5 +++ src/common/message/game-message.ts | 30 +++++++++++++ src/common/message/message.ts | 8 ++++ src/common/message/parse-message.ts | 6 +++ src/server/api/api.ts | 22 ++++++++++ src/server/api/queue.tsx | 53 +++++++++++++++++++++++ 8 files changed, 209 insertions(+) create mode 100644 src/client/setup/queue.tsx create mode 100644 src/server/api/queue.tsx diff --git a/src/client/index.scss b/src/client/index.scss index a2af4610..0f6cdfb7 100644 --- a/src/client/index.scss +++ b/src/client/index.scss @@ -27,3 +27,23 @@ position: absolute; // border: 3px solid blue; } + +.sidebar { + //w3 schools for the win + width: 20%; + height: 100%; + position: fixed; /* Fixed Sidebar (stay in place on scroll) */ + z-index: 1; /* Stay on top */ + top: 0; /* Stay at the top */ + left: 0; + background-color: #fff; /* Black */ + overflow-x: hidden; /* Disable horizontal scroll */ + padding-top: 20px; +} + +.main-dialog{ + margin-left: 20%; + position: fixed; + width: 80%; + height: 100%; +} diff --git a/src/client/setup/queue.tsx b/src/client/setup/queue.tsx new file mode 100644 index 00000000..6adbc79f --- /dev/null +++ b/src/client/setup/queue.tsx @@ -0,0 +1,65 @@ +import { Button, NonIdealState, Spinner } from "@blueprintjs/core"; +import { Dispatch, useState } from "react"; +import { MessageHandler } from "../../common/message/message"; +import { JoinQueue, UpdateQueue } from "../../common/message/game-message"; +import { get, useEffectQuery, useSocket } from "../api"; + +function getMessageHandler(setQueue: Dispatch): MessageHandler { + return (message) => { + console.log("cool message box"); + if (message instanceof UpdateQueue) { + console.log(message.queue); + setQueue(message.queue); + } + }; +} + +export function Sidebar(): JSX.Element { + const [queue, setQueue] = useState([]); + + const sendMessage = useSocket(getMessageHandler(setQueue)); + + const { isPending, data, isError } = useEffectQuery( + "get-queue", + async () => { + return get("/get-queue").then((newQueue) => { + setQueue(newQueue); + return newQueue; + + }); + }, + true, + ); + data; + if (isPending) { + return ( + } + title="Loading..." + /> + ); + } + if(isError){ + console.log(isError); + } + + return ( +
+

Player Queue

+
    + {queue.map(function (data) { + return
  • {data}
  • ; + })} +
+ +
+ ); +} diff --git a/src/client/setup/setup-base.tsx b/src/client/setup/setup-base.tsx index 95923558..daab07e7 100644 --- a/src/client/setup/setup-base.tsx +++ b/src/client/setup/setup-base.tsx @@ -4,6 +4,7 @@ import { ChessboardWrapper } from "../chessboard/chessboard-wrapper"; import { PropsWithChildren, ReactNode } from "react"; import { ChessEngine } from "../../common/chess-engine"; import { Side } from "../../common/game-types"; +import { Sidebar } from "./queue"; interface SetupBaseProps extends PropsWithChildren { actions?: ReactNode; @@ -12,6 +13,8 @@ interface SetupBaseProps extends PropsWithChildren { export function SetupBase(props: SetupBaseProps): JSX.Element { return ( <> + +
{props.children} +
); } diff --git a/src/common/message/game-message.ts b/src/common/message/game-message.ts index 732c921d..07a5b4da 100644 --- a/src/common/message/game-message.ts +++ b/src/common/message/game-message.ts @@ -84,3 +84,33 @@ export class GameHoldMessage extends Message { }; } } + +export class JoinQueue extends Message { + constructor(public readonly queue: string) { + super(); + } + + protected type = MessageType.JOIN_QUEUE; + + protected toObj(): object { + return { + ...super.toObj(), + queue: this.queue, + }; + } +} + +export class UpdateQueue extends Message { + constructor(public readonly queue: string[]) { + super(); + } + + protected type = MessageType.UPDATE_QUEUE; + + protected toObj(): object { + return { + ...super.toObj(), + queue: this.queue, + }; + } +} \ No newline at end of file diff --git a/src/common/message/message.ts b/src/common/message/message.ts index 923fa1a1..9efacec0 100644 --- a/src/common/message/message.ts +++ b/src/common/message/message.ts @@ -52,6 +52,14 @@ export enum MessageType { * A message sent from server to all clients for updating the robot simulator. */ SIMULATOR_UPDATE = "simulator-update", + /** + * A message for a client to join the game queue + */ + JOIN_QUEUE = 'join-queue', + /** + * A message for the server to update queues + */ + UPDATE_QUEUE = 'update-queue', } export abstract class Message { diff --git a/src/common/message/parse-message.ts b/src/common/message/parse-message.ts index 1d641860..e14e1a79 100644 --- a/src/common/message/parse-message.ts +++ b/src/common/message/parse-message.ts @@ -7,6 +7,8 @@ import { GameStartedMessage, GameHoldMessage, GameFinishedMessage, + JoinQueue, + UpdateQueue, } from "./game-message"; import { SimulatorUpdateMessage } from "./simulator-message"; @@ -34,6 +36,10 @@ export function parseMessage(text: string): Message { return new PositionMessage(obj.pgn); case MessageType.MOVE: return new MoveMessage(obj.move); + case MessageType.JOIN_QUEUE: + return new JoinQueue(obj.queue); + case MessageType.UPDATE_QUEUE: + return new UpdateQueue(obj.queue); case MessageType.DRIVE_ROBOT: return new DriveRobotMessage( obj.id, diff --git a/src/server/api/api.ts b/src/server/api/api.ts index 63186eee..5420b5bc 100644 --- a/src/server/api/api.ts +++ b/src/server/api/api.ts @@ -6,7 +6,9 @@ import { GameFinishedMessage, GameHoldMessage, GameInterruptedMessage, + JoinQueue, MoveMessage, + UpdateQueue, } from "../../common/message/game-message"; import { DriveRobotMessage, @@ -29,12 +31,15 @@ import { SaveManager } from "./save-manager"; import { VirtualBotTunnel, virtualRobots } from "../simulator"; import { Position } from "../robot/position"; import { DEGREE } from "../utils/units"; +import { PriorityQueue } from "./queue"; export const tcpServer: TCPServer | null = USE_VIRTUAL_ROBOTS ? null : new TCPServer(); export let gameManager: GameManager | null = null; +const queue = new PriorityQueue(); +const names = new Map(); /** * An endpoint used to establish a websocket connection with the server. * @@ -42,7 +47,10 @@ export let gameManager: GameManager | null = null; */ export const websocketHandler: WebsocketRequestHandler = (ws, req) => { ws.on("close", () => { + queue.popInd(queue.find(req.cookies.id)); + names.delete(req.cookies.id); socketManager.handleSocketClosed(req.cookies.id); + socketManager.sendToAll(new UpdateQueue([...names.values()])); }); ws.on("message", (data) => { @@ -63,12 +71,26 @@ export const websocketHandler: WebsocketRequestHandler = (ws, req) => { doDriveRobot(message); } else if (message instanceof SetRobotVariableMessage) { doSetRobotVariable(message); + } else if (message instanceof JoinQueue) { + const previous = queue.find(req.cookies.id); + names.set(req.cookies.id, message.queue); + previous ? queue : queue.insert(req.cookies.id, 0); + socketManager.sendToAll(new UpdateQueue([...names.values()])); } }); }; export const apiRouter = Router(); +apiRouter.get("/get-queue", (req, res) => { + req; + if(names) + return res.send([...names.values()]); + else{ + return res.send([]); + } +}); + apiRouter.get("/client-information", (req, res) => { const clientType = clientManager.getClientType(req.cookies.id); //loading saves from file if found diff --git a/src/server/api/queue.tsx b/src/server/api/queue.tsx new file mode 100644 index 00000000..aaf18b4d --- /dev/null +++ b/src/server/api/queue.tsx @@ -0,0 +1,53 @@ +// adapted from https://itnext.io/priority-queue-in-typescript-6ef23116901 +/** + * An priority queue class since ts doesn't have one + * + * Inverted for ease of use with pricing (0 is lowest priority) + */ +export class PriorityQueue { + private data: [number, T][] = []; + + public insert(item: T, priority: number): boolean { + if (this.data.length === 0) { + this.data.push([priority, item]); + return true; + } + + for (let index = 0; index < this.data.length; index++) { + if (index === this.data.length - 1) { + this.data.push([priority, item]); + return true; + } + + if (this.data[index][0] < priority) { + this.data.splice(index, 0, [priority, item]); + return true; + } + } + return false; + } + public peek(): T | undefined { + return this.data.length === 0 ? undefined : this.data[0][1]; + } + public pop(): T | undefined { + return this.data !== undefined ? this.data.pop()?.[1] : undefined; + } + public popInd(num: number | undefined): T { + return num !== undefined && this.data !== undefined ? + this.data[num].pop()?.[1] + : undefined; + } + public size(): number { + return this.data.length; + } + public isEmpty(): boolean { + return this.data.length === 0; + } + public find(entry: T) { + for (let x = 0; x < this.data.length; x++) { + if (this.data[x][1] === entry) { + return x; + } + } + } +} From 249382f5ee09e9b4a4a3dd3240321512a144cb4a Mon Sep 17 00:00:00 2001 From: ymmot239 <77180103+ymmot239@users.noreply.github.com> Date: Tue, 19 Nov 2024 12:17:14 -0600 Subject: [PATCH 02/15] Added comments and ran prettier again --- src/client/index.scss | 2 +- src/client/setup/queue.tsx | 14 ++++++++---- src/client/setup/setup-base.tsx | 36 +++++++++++++++--------------- src/common/message/game-message.ts | 2 +- src/common/message/message.ts | 4 ++-- src/server/api/api.ts | 8 ++++--- 6 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/client/index.scss b/src/client/index.scss index 0f6cdfb7..6c6794d2 100644 --- a/src/client/index.scss +++ b/src/client/index.scss @@ -41,7 +41,7 @@ padding-top: 20px; } -.main-dialog{ +.main-dialog { margin-left: 20%; position: fixed; width: 80%; diff --git a/src/client/setup/queue.tsx b/src/client/setup/queue.tsx index 6adbc79f..8a217809 100644 --- a/src/client/setup/queue.tsx +++ b/src/client/setup/queue.tsx @@ -14,6 +14,10 @@ function getMessageHandler(setQueue: Dispatch): MessageHandler { }; } +/** + * Creates a sidebar to hold the queue elements + * @returns sidebar container + */ export function Sidebar(): JSX.Element { const [queue, setQueue] = useState([]); @@ -25,11 +29,11 @@ export function Sidebar(): JSX.Element { return get("/get-queue").then((newQueue) => { setQueue(newQueue); return newQueue; - }); }, true, ); + //ts wanted me to do something with my data data; if (isPending) { return ( @@ -38,14 +42,16 @@ export function Sidebar(): JSX.Element { title="Loading..." /> ); - } - if(isError){ + } + if (isError) { console.log(isError); } return (
-

Player Queue

+

+ Player Queue +

    {queue.map(function (data) { return
  • {data}
  • ; diff --git a/src/client/setup/setup-base.tsx b/src/client/setup/setup-base.tsx index daab07e7..1ee88b21 100644 --- a/src/client/setup/setup-base.tsx +++ b/src/client/setup/setup-base.tsx @@ -13,25 +13,25 @@ interface SetupBaseProps extends PropsWithChildren { export function SetupBase(props: SetupBaseProps): JSX.Element { return ( <> - -
    - - {}} - rotation={0} - /> - - {props.children} - - +
    + + {}} + rotation={0} + /> + + {props.children} + +
    + ); } diff --git a/src/common/message/game-message.ts b/src/common/message/game-message.ts index 07a5b4da..7c07110d 100644 --- a/src/common/message/game-message.ts +++ b/src/common/message/game-message.ts @@ -113,4 +113,4 @@ export class UpdateQueue extends Message { queue: this.queue, }; } -} \ No newline at end of file +} diff --git a/src/common/message/message.ts b/src/common/message/message.ts index 9efacec0..4e0b1ffc 100644 --- a/src/common/message/message.ts +++ b/src/common/message/message.ts @@ -55,11 +55,11 @@ export enum MessageType { /** * A message for a client to join the game queue */ - JOIN_QUEUE = 'join-queue', + JOIN_QUEUE = "join-queue", /** * A message for the server to update queues */ - UPDATE_QUEUE = 'update-queue', + UPDATE_QUEUE = "update-queue", } export abstract class Message { diff --git a/src/server/api/api.ts b/src/server/api/api.ts index 5420b5bc..0eec7479 100644 --- a/src/server/api/api.ts +++ b/src/server/api/api.ts @@ -82,11 +82,13 @@ export const websocketHandler: WebsocketRequestHandler = (ws, req) => { export const apiRouter = Router(); +/** + * gets the current stored queue + */ apiRouter.get("/get-queue", (req, res) => { req; - if(names) - return res.send([...names.values()]); - else{ + if (names) return res.send([...names.values()]); + else { return res.send([]); } }); From 071655cf071ad9ca414ef9a2a710237b8b87214b Mon Sep 17 00:00:00 2001 From: ymmot239 <77180103+ymmot239@users.noreply.github.com> Date: Tue, 19 Nov 2024 13:52:00 -0600 Subject: [PATCH 03/15] Fixed queue problem Did you know: our web socket has a share function that allows multiple connections! without it, any connections added that are repeats will be ignored. Anyways the queue screen works and is now in the queue file. --- src/client/api.ts | 1 + src/client/setup/setup-base.tsx | 2 +- src/client/setup/{queue.tsx => sidebar.tsx} | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) rename src/client/setup/{queue.tsx => sidebar.tsx} (97%) diff --git a/src/client/api.ts b/src/client/api.ts index 2f9d76ee..497a86b6 100644 --- a/src/client/api.ts +++ b/src/client/api.ts @@ -58,6 +58,7 @@ export function useSocket( onMessage(message); } }, + share: true, }); const sendMessageHandler = useMemo(() => { diff --git a/src/client/setup/setup-base.tsx b/src/client/setup/setup-base.tsx index 1ee88b21..9f3a0638 100644 --- a/src/client/setup/setup-base.tsx +++ b/src/client/setup/setup-base.tsx @@ -4,7 +4,7 @@ import { ChessboardWrapper } from "../chessboard/chessboard-wrapper"; import { PropsWithChildren, ReactNode } from "react"; import { ChessEngine } from "../../common/chess-engine"; import { Side } from "../../common/game-types"; -import { Sidebar } from "./queue"; +import { Sidebar } from "./sidebar"; interface SetupBaseProps extends PropsWithChildren { actions?: ReactNode; diff --git a/src/client/setup/queue.tsx b/src/client/setup/sidebar.tsx similarity index 97% rename from src/client/setup/queue.tsx rename to src/client/setup/sidebar.tsx index 8a217809..86d98031 100644 --- a/src/client/setup/queue.tsx +++ b/src/client/setup/sidebar.tsx @@ -9,7 +9,7 @@ function getMessageHandler(setQueue: Dispatch): MessageHandler { console.log("cool message box"); if (message instanceof UpdateQueue) { console.log(message.queue); - setQueue(message.queue); + setQueue(message.queue.slice()); } }; } @@ -22,7 +22,7 @@ export function Sidebar(): JSX.Element { const [queue, setQueue] = useState([]); const sendMessage = useSocket(getMessageHandler(setQueue)); - + const { isPending, data, isError } = useEffectQuery( "get-queue", async () => { From 6485c2e62f6a16936c65edd6ca4614cc3b70abd2 Mon Sep 17 00:00:00 2001 From: ymmot239 <77180103+ymmot239@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:20:08 -0600 Subject: [PATCH 04/15] Fixed reload Made it so someone is removed from the queue if they are disconnected for 2 seconds aka they close the tab, not just reload. --- src/client/setup/sidebar.tsx | 2 +- src/server/api/api.ts | 10 +++++++--- src/server/main.ts | 2 ++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/client/setup/sidebar.tsx b/src/client/setup/sidebar.tsx index 86d98031..71d825c2 100644 --- a/src/client/setup/sidebar.tsx +++ b/src/client/setup/sidebar.tsx @@ -22,7 +22,7 @@ export function Sidebar(): JSX.Element { const [queue, setQueue] = useState([]); const sendMessage = useSocket(getMessageHandler(setQueue)); - + const { isPending, data, isError } = useEffectQuery( "get-queue", async () => { diff --git a/src/server/api/api.ts b/src/server/api/api.ts index 0eec7479..6962523e 100644 --- a/src/server/api/api.ts +++ b/src/server/api/api.ts @@ -47,10 +47,14 @@ const names = new Map(); */ export const websocketHandler: WebsocketRequestHandler = (ws, req) => { ws.on("close", () => { - queue.popInd(queue.find(req.cookies.id)); - names.delete(req.cookies.id); socketManager.handleSocketClosed(req.cookies.id); - socketManager.sendToAll(new UpdateQueue([...names.values()])); + setTimeout(() => { + if (socketManager.getSocket(req.cookies.id) === undefined) { + queue.popInd(queue.find(req.cookies.id)); + names.delete(req.cookies.id); + socketManager.sendToAll(new UpdateQueue([...names.values()])); + } + }, 2000); }); ws.on("message", (data) => { diff --git a/src/server/main.ts b/src/server/main.ts index 411fee25..ca248b55 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -54,3 +54,5 @@ app.use("/api", apiRouter); ViteExpress.listen(app as unknown as Express, 3000, () => { console.log("Server is listening on port 3000."); }); + +app.addListener("beforeunload", () => {}); From dab1559f1554cc164988dbe37b85b66fcb620cdb Mon Sep 17 00:00:00 2001 From: ymmot239 <77180103+ymmot239@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:00:52 -0600 Subject: [PATCH 05/15] Added host reassignment Allows the host to be reassigned based on who is first in line. Will end a game if someone disconnects for over 5 seconds. Need to: Move people to the end of the line after a game. Allow people to choose their names. Make the queue phone friendly. --- src/client/game/game.tsx | 26 ++++++++------ src/client/setup/lobby.tsx | 6 +++- src/client/setup/sidebar.tsx | 1 - src/server/api/api.ts | 59 +++++++++++++++++++++++++++++--- src/server/api/client-manager.ts | 16 +++++++++ src/server/api/queue.tsx | 14 +++++--- 6 files changed, 99 insertions(+), 23 deletions(-) diff --git a/src/client/game/game.tsx b/src/client/game/game.tsx index 0bf88112..3d737d6f 100644 --- a/src/client/game/game.tsx +++ b/src/client/game/game.tsx @@ -22,6 +22,7 @@ import { ChessEngine } from "../../common/chess-engine"; import { Move } from "../../common/game-types"; import { NonIdealState, Spinner } from "@blueprintjs/core"; import { AcceptDrawDialog, OfferDrawDialog } from "./draw-dialog"; +import { Sidebar } from "../setup/sidebar"; /** * Creates a MessageHandler function. @@ -127,17 +128,20 @@ export function Game(): JSX.Element { side={side} setRotation={setRotation} /> -
    - - {gameEndDialog} - {gameOfferDialog} - {gameAcceptDialog} - + +
    +
    + + {gameEndDialog} + {gameOfferDialog} + {gameAcceptDialog} + +
    ); diff --git a/src/client/setup/lobby.tsx b/src/client/setup/lobby.tsx index 05a023a6..80ed7843 100644 --- a/src/client/setup/lobby.tsx +++ b/src/client/setup/lobby.tsx @@ -35,7 +35,11 @@ export function Lobby() { return ( } /> diff --git a/src/client/setup/sidebar.tsx b/src/client/setup/sidebar.tsx index 71d825c2..aa7f6811 100644 --- a/src/client/setup/sidebar.tsx +++ b/src/client/setup/sidebar.tsx @@ -6,7 +6,6 @@ import { get, useEffectQuery, useSocket } from "../api"; function getMessageHandler(setQueue: Dispatch): MessageHandler { return (message) => { - console.log("cool message box"); if (message instanceof UpdateQueue) { console.log(message.queue); setQueue(message.queue.slice()); diff --git a/src/server/api/api.ts b/src/server/api/api.ts index 6962523e..cc557095 100644 --- a/src/server/api/api.ts +++ b/src/server/api/api.ts @@ -6,6 +6,7 @@ import { GameFinishedMessage, GameHoldMessage, GameInterruptedMessage, + GameStartedMessage, JoinQueue, MoveMessage, UpdateQueue, @@ -16,7 +17,7 @@ import { } from "../../common/message/robot-message"; import { TCPServer } from "./tcp-interface"; -import { Difficulty } from "../../common/client-types"; +import { ClientType, Difficulty } from "../../common/client-types"; import { RegisterWebsocketMessage } from "../../common/message/message"; import { clientManager, robotManager, socketManager } from "./managers"; import { @@ -32,6 +33,7 @@ import { VirtualBotTunnel, virtualRobots } from "../simulator"; import { Position } from "../robot/position"; import { DEGREE } from "../utils/units"; import { PriorityQueue } from "./queue"; +import { GameInterruptedReason } from "../../common/game-end-reasons"; export const tcpServer: TCPServer | null = USE_VIRTUAL_ROBOTS ? null : new TCPServer(); @@ -48,11 +50,55 @@ const names = new Map(); export const websocketHandler: WebsocketRequestHandler = (ws, req) => { ws.on("close", () => { socketManager.handleSocketClosed(req.cookies.id); + //wait in case the client is just reloading or disconnected instead of leaving setTimeout(() => { if (socketManager.getSocket(req.cookies.id) === undefined) { + //remove the person from the queue to free up space queue.popInd(queue.find(req.cookies.id)); names.delete(req.cookies.id); + const clientType = clientManager.getClientType(req.cookies.id); + + //if the person was a host / client, a new one needs to be reassigned + if (clientManager.isPlayer(req.cookies.id)) { + //clear the existing game + const ids = clientManager.getIds(); + if (ids) { + if ( + SaveManager.loadGame(req.cookies.id)?.host === + ids[0] + ) + SaveManager.endGame(ids[0], ids[1]); + else SaveManager.endGame(ids[1], ids[0]); + } + + gameManager = null; + + //remove the old host/client + clientType === ClientType.HOST ? + clientManager.removeHost() + : clientManager.removeClient(); + + //if there exists someone to take their place + const newPlayer = queue.pop(); + if (newPlayer) { + //transfer them from spectator to the newly-opened spot and remove them from queue + clientManager.removeSpectator(newPlayer); + clientManager.assignPlayer(newPlayer); + names.delete(newPlayer); + socketManager.sendToAll( + new GameInterruptedMessage( + GameInterruptedReason.ABORTED, + ), + ); + } + //else they were a spectator and don't need game notifications anymore + } else { + clientManager.removeSpectator(req.cookies.id); + } + + //update the queue and reload all the pages socketManager.sendToAll(new UpdateQueue([...names.values()])); + socketManager.sendToAll(new GameStartedMessage()); } }, 2000); }); @@ -76,10 +122,13 @@ export const websocketHandler: WebsocketRequestHandler = (ws, req) => { } else if (message instanceof SetRobotVariableMessage) { doSetRobotVariable(message); } else if (message instanceof JoinQueue) { - const previous = queue.find(req.cookies.id); - names.set(req.cookies.id, message.queue); - previous ? queue : queue.insert(req.cookies.id, 0); - socketManager.sendToAll(new UpdateQueue([...names.values()])); + if (!clientManager.isPlayer(req.cookies.id)) { + if (queue.find(req.cookies.id) === undefined) { + queue.insert(req.cookies.id, 0); + } + names.set(req.cookies.id, message.queue); + socketManager.sendToAll(new UpdateQueue([...names.values()])); + } } }); }; diff --git a/src/server/api/client-manager.ts b/src/server/api/client-manager.ts index b40de054..4e3e9bc0 100644 --- a/src/server/api/client-manager.ts +++ b/src/server/api/client-manager.ts @@ -74,6 +74,22 @@ export class ClientManager { } } + public removeHost(): void { + this.hostId = undefined; + } + + public removeClient(): void { + this.clientId = undefined; + } + + public removeSpectator(id: string): void { + this.spectatorIds.delete(id); + } + + public isPlayer(id: string): boolean { + return id === this.hostId || id === this.clientId; + } + public getIds(): undefined | string[] { if (this.hostId && this.clientId) { return [this.hostId, this.clientId]; diff --git a/src/server/api/queue.tsx b/src/server/api/queue.tsx index aaf18b4d..1c44f37b 100644 --- a/src/server/api/queue.tsx +++ b/src/server/api/queue.tsx @@ -30,12 +30,15 @@ export class PriorityQueue { return this.data.length === 0 ? undefined : this.data[0][1]; } public pop(): T | undefined { - return this.data !== undefined ? this.data.pop()?.[1] : undefined; + return this.data !== undefined ? this.data.shift()?.[1] : undefined; } - public popInd(num: number | undefined): T { - return num !== undefined && this.data !== undefined ? - this.data[num].pop()?.[1] - : undefined; + public popInd(num: number | undefined): T | undefined { + if (num !== undefined && this.data !== undefined) { + const data = this.data[num][1]; + this.data.slice(0, num).concat(this.data.slice(num + 1, -1)); + return data; + } + return undefined; } public size(): number { return this.data.length; @@ -49,5 +52,6 @@ export class PriorityQueue { return x; } } + return undefined; } } From 636c577d7b592977ad1c52132bd3d5c72dda9a79 Mon Sep 17 00:00:00 2001 From: ymmot239 <77180103+ymmot239@users.noreply.github.com> Date: Fri, 22 Nov 2024 13:00:42 -0600 Subject: [PATCH 06/15] Added game end reasoning Allows queue to move hosts when the game ends. Tested with 5 clients. Need to: Allow users to choose a name. make queue ui look better --- src/server/api/api.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/server/api/api.ts b/src/server/api/api.ts index cc557095..af7dd910 100644 --- a/src/server/api/api.ts +++ b/src/server/api/api.ts @@ -42,6 +42,10 @@ export let gameManager: GameManager | null = null; const queue = new PriorityQueue(); const names = new Map(); + +//let the queue be moved once per game +let onlyOnce = true; + /** * An endpoint used to establish a websocket connection with the server. * @@ -50,6 +54,31 @@ const names = new Map(); export const websocketHandler: WebsocketRequestHandler = (ws, req) => { ws.on("close", () => { socketManager.handleSocketClosed(req.cookies.id); + + //if you reload and the game is over + if (gameManager?.isGameEnded() && onlyOnce) { + onlyOnce = false; + for (const x in [1, 2]) { + console.log(x); + if (!queue.isEmpty()) { + const newHost = clientManager.getIds(); + clientManager.removeHost(); + clientManager.removeClient(); + newHost ? + clientManager.assignPlayer(newHost[1]) + : undefined; + const newPlayer = queue.pop(); + if (newPlayer) { + //transfer them from spectator to the newly-opened spot and remove them from queue + clientManager.removeSpectator(newPlayer); + clientManager.assignPlayer(newPlayer); + names.delete(newPlayer); + socketManager.sendToAll(new GameStartedMessage()); + } + } + } + } + //wait in case the client is just reloading or disconnected instead of leaving setTimeout(() => { if (socketManager.getSocket(req.cookies.id) === undefined) { @@ -195,6 +224,7 @@ apiRouter.get("/game-state", (req, res) => { }); apiRouter.post("/start-computer-game", (req, res) => { + onlyOnce = true; const side = req.query.side as Side; const difficulty = parseInt(req.query.difficulty as string) as Difficulty; gameManager = new ComputerGameManager( @@ -208,6 +238,7 @@ apiRouter.post("/start-computer-game", (req, res) => { }); apiRouter.post("/start-human-game", (req, res) => { + onlyOnce = true; const side = req.query.side as Side; gameManager = new HumanGameManager( new ChessEngine(), From ad509f1f8fb6b4351412569055cd8f41cf3fae41 Mon Sep 17 00:00:00 2001 From: ymmot239 <77180103+ymmot239@users.noreply.github.com> Date: Fri, 22 Nov 2024 14:23:55 -0600 Subject: [PATCH 07/15] Queue names Allow users to choose their name that shows up in queue To do: make ui look good --- src/client/setup/setup-base.tsx | 1 + src/client/setup/sidebar.tsx | 20 +++++++++++++++++++- src/server/api/api.ts | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/client/setup/setup-base.tsx b/src/client/setup/setup-base.tsx index 9f3a0638..ebd3165f 100644 --- a/src/client/setup/setup-base.tsx +++ b/src/client/setup/setup-base.tsx @@ -26,6 +26,7 @@ export function SetupBase(props: SetupBaseProps): JSX.Element { canEscapeKeyClose={false} canOutsideClickClose={false} usePortal={false} + enforceFocus={false} > {props.children} diff --git a/src/client/setup/sidebar.tsx b/src/client/setup/sidebar.tsx index aa7f6811..cca58f7e 100644 --- a/src/client/setup/sidebar.tsx +++ b/src/client/setup/sidebar.tsx @@ -19,6 +19,9 @@ function getMessageHandler(setQueue: Dispatch): MessageHandler { */ export function Sidebar(): JSX.Element { const [queue, setQueue] = useState([]); + const [name, setName] = useState( + "player " + Math.floor(Math.random() * 2000), + ); const sendMessage = useSocket(getMessageHandler(setQueue)); @@ -56,11 +59,26 @@ export function Sidebar(): JSX.Element { return
  • {data}
  • ; })}
+ + +
); } diff --git a/src/server/api/api.ts b/src/server/api/api.ts index 1c9955e3..13b6000c 100644 --- a/src/server/api/api.ts +++ b/src/server/api/api.ts @@ -167,12 +167,17 @@ export const apiRouter = Router(); /** * gets the current stored queue */ -apiRouter.get("/get-queue", (req, res) => { - req; +apiRouter.get("/get-queue", (_, res) => { if (names) return res.send([...names.values()]); - else { - return res.send([]); - } + else return res.send([]); +}); + +/** + * gets the name associated with the request cookie + */ +apiRouter.get("/get-name", (req, res) => { + if (names) return res.send({ message: names.get(req.cookies.id) }); + else return res.send(""); }); apiRouter.get("/client-information", (req, res) => { From c9dc430f3d592ea77eb7f7ed6e664ddb9d6ab125 Mon Sep 17 00:00:00 2001 From: ymmot239 <77180103+ymmot239@users.noreply.github.com> Date: Mon, 2 Dec 2024 18:19:59 -0600 Subject: [PATCH 09/15] Sidebar in-game formatting adjusted the formatting for the sidebar in games so it doesn't get hidden under the navbar --- src/client/game/game.tsx | 2 +- src/client/index.scss | 2 ++ src/client/setup/sidebar.tsx | 11 +++++++++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/client/game/game.tsx b/src/client/game/game.tsx index 3d737d6f..94e972c7 100644 --- a/src/client/game/game.tsx +++ b/src/client/game/game.tsx @@ -128,7 +128,7 @@ export function Game(): JSX.Element { side={side} setRotation={setRotation} /> - +
): MessageHandler { }; } +interface sidebarProps { + top?: number | undefined; +} + /** * Creates a sidebar to hold the queue elements * @returns sidebar container */ -export function Sidebar(): JSX.Element { +export function Sidebar(props: sidebarProps): JSX.Element { const [queue, setQueue] = useState([]); const [name, setName] = useState( "player " + Math.floor(Math.random() * 10000), @@ -62,7 +66,10 @@ export function Sidebar(): JSX.Element { } return ( -
+

Player Queue

    {queue.map(function (data) { From 6374d1050fde6923cd3245f76d7ef777da96aac2 Mon Sep 17 00:00:00 2001 From: ymmot239 Date: Mon, 31 Mar 2025 18:17:43 -0500 Subject: [PATCH 10/15] Readability Changes made the code for managing new hosts clearer. removed a duplicate loading spinner in the sidebar --- src/client/setup/sidebar.tsx | 7 +---- src/server/api/api.ts | 48 ++++++++++++++++++++++++-------- src/server/api/client-manager.ts | 2 +- 3 files changed, 39 insertions(+), 18 deletions(-) diff --git a/src/client/setup/sidebar.tsx b/src/client/setup/sidebar.tsx index 4d2d58c1..4f066f6f 100644 --- a/src/client/setup/sidebar.tsx +++ b/src/client/setup/sidebar.tsx @@ -55,12 +55,7 @@ export function Sidebar(props: sidebarProps): JSX.Element { //ts wanted me to do something with my data data; if (isPending || names.isPending) { - return ( - } - title="Loading..." - /> - ); + return ; } else if (isError) { console.log(isError); } diff --git a/src/server/api/api.ts b/src/server/api/api.ts index 13b6000c..06e3ebce 100644 --- a/src/server/api/api.ts +++ b/src/server/api/api.ts @@ -57,23 +57,49 @@ export const websocketHandler: WebsocketRequestHandler = (ws, req) => { //if you reload and the game is over if (gameManager?.isGameEnded() && onlyOnce) { + //make the reassignment occur once per game instead of once per reload onlyOnce = false; - for (const x in [1, 2]) { - console.log(x); - if (!queue.isEmpty()) { - const newHost = clientManager.getIds(); - clientManager.removeHost(); - clientManager.removeClient(); - newHost ? - clientManager.assignPlayer(newHost[1]) - : undefined; + + //remove the old players and store them for future reference + const oldPlayers = clientManager.getIds(); + clientManager.removeHost(); + clientManager.removeClient(); + + if (oldPlayers !== undefined) { + //in most cases, the second player becomes the host + clientManager.assignPlayer(oldPlayers[1]); + + //if no one else wants to play, the host just swaps + if (queue.size() === 0) { + clientManager.assignPlayer(oldPlayers[0]); + } + + //if there is one person who wants to play, host moves to the second player + if (queue.size() === 1) { const newPlayer = queue.pop(); if (newPlayer) { - //transfer them from spectator to the newly-opened spot and remove them from queue clientManager.removeSpectator(newPlayer); clientManager.assignPlayer(newPlayer); names.delete(newPlayer); - socketManager.sendToAll(new GameStartedMessage()); + } + } + + //are enough people to start a game, forget the old people + if (queue.size() >= 2) { + const newPlayer = queue.pop(); + const newSecondPlayer = queue.pop(); + if (newPlayer && newSecondPlayer) { + //reset the clients + clientManager.removeHost(); + clientManager.removeClient(); + + //assign new players + clientManager.removeSpectator(newPlayer); + clientManager.assignPlayer(newPlayer); + names.delete(newPlayer); + clientManager.removeSpectator(newSecondPlayer); + clientManager.assignPlayer(newSecondPlayer); + names.delete(newSecondPlayer); } } } diff --git a/src/server/api/client-manager.ts b/src/server/api/client-manager.ts index 4e3e9bc0..8bdb9352 100644 --- a/src/server/api/client-manager.ts +++ b/src/server/api/client-manager.ts @@ -91,7 +91,7 @@ export class ClientManager { } public getIds(): undefined | string[] { - if (this.hostId && this.clientId) { + if (this.hostId !== undefined && this.clientId !== undefined) { return [this.hostId, this.clientId]; } else { return; From 7962d6132b1a7daf0acabc9d032ed3b052891b0a Mon Sep 17 00:00:00 2001 From: ymmot239 Date: Mon, 31 Mar 2025 18:30:52 -0500 Subject: [PATCH 11/15] Update index.scss changed media size and added padding to fix queue overlap --- src/client/index.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/client/index.scss b/src/client/index.scss index 35fd1e17..0af38a40 100644 --- a/src/client/index.scss +++ b/src/client/index.scss @@ -46,6 +46,7 @@ position: fixed; width: 80%; height: 100%; + padding-top: 50px; } .flex-container { @@ -63,7 +64,7 @@ margin-top: auto; } -@media (max-width: 1000px) { +@media (max-width: 500px) { .flex-container { flex-direction: row; } @@ -86,5 +87,6 @@ .main-dialog { width: 100%; margin-left: auto; + padding-top: 0px; } } From 3f437477cecb5a17191a2d9fbc918606588613a2 Mon Sep 17 00:00:00 2001 From: ymmot239 Date: Mon, 31 Mar 2025 18:37:03 -0500 Subject: [PATCH 12/15] fixed lint check removed an unused import --- src/client/setup/sidebar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/setup/sidebar.tsx b/src/client/setup/sidebar.tsx index 4f066f6f..e0c74a1f 100644 --- a/src/client/setup/sidebar.tsx +++ b/src/client/setup/sidebar.tsx @@ -1,4 +1,4 @@ -import { Button, NonIdealState, Spinner } from "@blueprintjs/core"; +import { Button, NonIdealState } from "@blueprintjs/core"; import { Dispatch, useState } from "react"; import { MessageHandler } from "../../common/message/message"; import { JoinQueue, UpdateQueue } from "../../common/message/game-message"; From 3cee1b582e306c5bef42e3d1b7ea9a1b1a25ef9d Mon Sep 17 00:00:00 2001 From: ymmot239 Date: Mon, 31 Mar 2025 20:01:53 -0500 Subject: [PATCH 13/15] added dark mode support Added all the dark mode functions to the sidebar and waiting screens. Moved the theme buttons to a variable for use in multiple places. --- src/client/index.scss | 1 - src/client/setup/lobby.tsx | 30 ++++++++++++++++++++++-------- src/client/setup/setup-base.tsx | 12 ++++++------ src/client/setup/setup.tsx | 33 ++++++++++++++++++++------------- src/client/setup/sidebar.tsx | 22 +++++++++++++++++----- 5 files changed, 65 insertions(+), 33 deletions(-) diff --git a/src/client/index.scss b/src/client/index.scss index 0af38a40..c37f6909 100644 --- a/src/client/index.scss +++ b/src/client/index.scss @@ -36,7 +36,6 @@ z-index: 1; /* Stay on top */ top: 0; /* Stay at the top */ left: 0; - background-color: #fff; /* Black */ overflow-x: hidden; /* Disable horizontal scroll */ padding-top: 20px; } diff --git a/src/client/setup/lobby.tsx b/src/client/setup/lobby.tsx index 3e68fbd8..fedb426c 100644 --- a/src/client/setup/lobby.tsx +++ b/src/client/setup/lobby.tsx @@ -5,6 +5,7 @@ import { GameStartedMessage } from "../../common/message/game-message"; import { useSocket, useEffectQuery, get } from "../api"; import { ClientType } from "../../common/client-types"; import { Navigate } from "react-router-dom"; +import { themeButtons } from "./setup"; /** * check for an active game and waits for one or forwards to setup @@ -42,14 +43,27 @@ export function Lobby() { } else { return ( - } - /> + <> + } + /> +
    + {themeButtons} +
    +
    ); } diff --git a/src/client/setup/setup-base.tsx b/src/client/setup/setup-base.tsx index 59b237aa..9cdef379 100644 --- a/src/client/setup/setup-base.tsx +++ b/src/client/setup/setup-base.tsx @@ -31,20 +31,20 @@ export function SetupBase(props: SetupBaseProps): JSX.Element { />
    - {props.children} + {props.children}
    -
    +
diff --git a/src/client/setup/setup.tsx b/src/client/setup/setup.tsx index e4070f54..eb2180de 100644 --- a/src/client/setup/setup.tsx +++ b/src/client/setup/setup.tsx @@ -127,19 +127,7 @@ function SetupMain(props: SetupMainProps) { onClick={() => props.onPageChange(SetupType.PUZZLE)} className={buttonColor()} /> -

Display Settings:

- - {allSettings.map((item, idx) => ( -
); } + +export const themeButtons = ( + <> +

Display Settings:

+ + {allSettings.map((item, idx) => ( + From f3f76ad0118da48be8228f586d9aa139e5652577 Mon Sep 17 00:00:00 2001 From: ymmot239 Date: Mon, 31 Mar 2025 20:12:37 -0500 Subject: [PATCH 14/15] fixed theme buttons apparently JSX.Elements are required to be capitalized --- src/client/setup/lobby.tsx | 4 ++-- src/client/setup/setup.tsx | 39 ++++++++++++++++++++------------------ 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/client/setup/lobby.tsx b/src/client/setup/lobby.tsx index fedb426c..352bd262 100644 --- a/src/client/setup/lobby.tsx +++ b/src/client/setup/lobby.tsx @@ -5,7 +5,7 @@ import { GameStartedMessage } from "../../common/message/game-message"; import { useSocket, useEffectQuery, get } from "../api"; import { ClientType } from "../../common/client-types"; import { Navigate } from "react-router-dom"; -import { themeButtons } from "./setup"; +import { ThemeButtons } from "./setup"; /** * check for an active game and waits for one or forwards to setup @@ -61,7 +61,7 @@ export function Lobby() { justifyContent: "space-around", }} > - {themeButtons} +
diff --git a/src/client/setup/setup.tsx b/src/client/setup/setup.tsx index eb2180de..160bdf7d 100644 --- a/src/client/setup/setup.tsx +++ b/src/client/setup/setup.tsx @@ -127,7 +127,7 @@ function SetupMain(props: SetupMainProps) { onClick={() => props.onPageChange(SetupType.PUZZLE)} className={buttonColor()} /> - {themeButtons} + ); @@ -152,20 +152,23 @@ function SetupMain(props: SetupMainProps) { ); } -export const themeButtons = ( - <> -

Display Settings:

- - {allSettings.map((item, idx) => ( -