Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
f7836a3
Added pause dialogs
ymmot239 Sep 9, 2025
cafcf45
Notification dialog
ymmot239 Sep 9, 2025
e384175
Server pause structure
ymmot239 Sep 9, 2025
f4b9f52
CHESSBOTS BEGINS NOW!!!! 🗣️🗣️🗣️🔥🔥🔥
MahdMalik Sep 15, 2025
32a4936
CHESSBOTS BEGINS NOW!!!! 🗣️🗣️🗣️🔥🔥🔥
MahdMalik Sep 15, 2025
5991b28
issue should be done less go
MahdMalik Sep 15, 2025
1a64c4b
fixed es lint and various errors
MahdMalik Sep 15, 2025
281f28c
ok now it should actually be fixed
MahdMalik Sep 15, 2025
d87139b
Merge branch 'main' into admin-pause-play
ymmot239 Sep 16, 2025
b3819e3
Merge branch 'admin-pause-play' of https://github.com/Comet-Robotics/…
MahdMalik Sep 16, 2025
a872c3f
advanced stop procedure
ymmot239 Sep 22, 2025
2439114
Merge branch 'main' of https://github.com/Comet-Robotics/chessbots-se…
MahdMalik Sep 22, 2025
7e9a4fa
changed the pauseGame endpoint to also work if the server side calls …
MahdMalik Sep 22, 2025
eeecb63
theoretically may have pausing of the game working, gonna test now. I…
MahdMalik Sep 22, 2025
9fccb54
added robot saves
ymmot239 Sep 22, 2025
072b3a4
Merge branch 'main' into admin-pause-play
ymmot239 Sep 22, 2025
fbc9ec7
integrating pause/unpause with bot disconnects/connects should be goo…
MahdMalik Sep 23, 2025
96361c4
task should be done now, accounting for differnet people pausing. It'…
MahdMalik Sep 23, 2025
84f702d
moved some variables to managers.ts, just need to fix some ugly code …
MahdMalik Sep 23, 2025
ee0153c
task finished maybe
MahdMalik Sep 23, 2025
93b09d7
task is pretty mucch done, just gotta pass hte eslint check things.
MahdMalik Sep 23, 2025
c2d7b5e
Added pause rollback
ymmot239 Sep 23, 2025
e166cf9
pause on reload
ymmot239 Sep 23, 2025
7e626b1
alright, fixed eslint and format errors. Task should be done?
MahdMalik Sep 23, 2025
17ec3c3
Merging play features into bot-pause-resume (#262)
MahdMalik Sep 23, 2025
d128db4
just finished merging, there is kind of an issue with how I'm going t…
MahdMalik Sep 23, 2025
c42eca4
got a workingg solution for now, I want to see if I can make it bette…
MahdMalik Sep 24, 2025
c2dfc02
Fixed merge conflicts
ymmot239 Sep 25, 2025
95d192c
Execution bugfix
ymmot239 Sep 25, 2025
d0d7456
Merge branch 'admin-pause-play' of https://github.com/Comet-Robotics/…
MahdMalik Sep 29, 2025
faa5547
nearly done, final thing is figuring out how I should do the follback…
MahdMalik Sep 29, 2025
2d8d60d
paushing/resuming on bot disconnect/connect works now but I used an e…
MahdMalik Sep 29, 2025
ab1bb1c
changed it to have two exported functions as api.ts instead, this is …
MahdMalik Sep 29, 2025
4d4def1
I HATE DEPENDENCY CYCLES
MahdMalik Sep 29, 2025
8ae2199
TASK FINALLY FINISHED, CHESSBOTS BEGINS NOW!!!!!
MahdMalik Sep 29, 2025
d14416d
fixed issue with it keeping robot data even after disconnect, should …
MahdMalik Sep 30, 2025
0a8b8f4
task is done I think?
MahdMalik Sep 30, 2025
89309d6
1984. They don't like the parameter name theFlag.
MahdMalik Sep 30, 2025
72f27d8
rmeove weird 2
MahdMalik Sep 30, 2025
b8c0c7f
fixed some type non-declarations too
MahdMalik Sep 30, 2025
faaa991
fixed formatting
MahdMalik Sep 30, 2025
12b905b
ok actually fixed copilot's PR comments fully
MahdMalik Sep 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions src/client/game/admin-dialogs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {
Button,
Dialog,
DialogBody,
DialogFooter,
NonIdealState,
} from "@blueprintjs/core";
import { useState } from "react";
import { bgColor, buttonColor, textColor } from "../check-dark-mode";

interface DrawDialogProps {
dialogText?: string;
}

/**
* Shows a paused dialog that cannot be closed
* @param props - dialog text
* @returns - pause dialog
*/
export function PauseDialog(props: DrawDialogProps) {
const [isOpen, setIsOpen] = useState(true);
return (
<Dialog
className={bgColor()}
isOpen={isOpen}
onClose={() => setIsOpen(false)}
canOutsideClickClose={false}
canEscapeKeyClose={false}
>
<DialogBody>
<NonIdealState>
<h4 className={textColor()}>
{props.dialogText || "Game Paused"}
</h4>
</NonIdealState>
</DialogBody>
</Dialog>
);
}

interface NotificationDialogProps {
dialogText: string;
}

/**
* Shows a closable notification dialog
* @param props - dialog text
* @returns - notification dialog
*/
export function NotificationDialog(props: NotificationDialogProps) {
const [isOpen, setIsOpen] = useState(true);

/** okay button */
const actions = (
<Button
text="Continue"
rightIcon="arrow-right"
className={buttonColor()}
intent="primary"
onClick={() => {
setIsOpen(false);
}}
/>
);

return (
<Dialog
className={bgColor()}
isOpen={isOpen}
onClose={() => setIsOpen(false)}
canOutsideClickClose={true}
canEscapeKeyClose={true}
>
<DialogBody>
<NonIdealState>
<h4 className={textColor()}>{props.dialogText}</h4>
</NonIdealState>
</DialogBody>
<DialogFooter minimal actions={actions} />
</Dialog>
);
}
43 changes: 35 additions & 8 deletions src/client/game/game.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { NonIdealState, Spinner } from "@blueprintjs/core";
import { AcceptDrawDialog, OfferDrawDialog } from "./draw-dialog";
import { bgColor } from "../check-dark-mode";
import "../colors.css";
import { NotificationDialog, PauseDialog } from "./admin-dialogs";
import { PuzzleTipBox } from "../PuzzleTipBox";

/**
Expand All @@ -41,6 +42,7 @@ function getMessageHandler(
setGameInterruptedReason: Dispatch<GameInterruptedReason>,
setGameEndedReason: Dispatch<GameEndReason>,
setGameHoldReason: Dispatch<GameHoldReason>,
setPaused: Dispatch<boolean>,
): MessageHandler {
return (message) => {
if (message instanceof MoveMessage) {
Expand All @@ -63,6 +65,11 @@ function getMessageHandler(
setGameEndedReason(message.reason);
} else if (message instanceof GameHoldMessage) {
setGameHoldReason(message.reason);
if (message.reason === GameHoldReason.GAME_PAUSED) {
setPaused(true);
} else if (message.reason === GameHoldReason.GAME_UNPAUSED) {
setPaused(false);
}
}
};
}
Expand All @@ -78,6 +85,7 @@ export function Game(): JSX.Element {
const [gameEndedReason, setGameEndedReason] = useState<GameEndReason>();
const [gameHoldReason, setGameHoldReason] = useState<GameHoldReason>();
const [rotation, setRotation] = useState<number>(0);
const [paused, setPause] = useState<boolean>(false);

/** send any messages using our defined message handler inside a message socket for handling */
const sendMessage = useSocket(
Expand All @@ -87,6 +95,7 @@ export function Game(): JSX.Element {
setGameInterruptedReason,
setGameEndedReason,
setGameHoldReason,
setPause,
),
);

Expand All @@ -95,11 +104,12 @@ export function Game(): JSX.Element {
"game-state",
async () => {
return get("/game-state").then((gameState) => {
setChess(new ChessEngine(gameState.position));
if (gameState.gameEndReason !== undefined) {
setGameInterruptedReason(gameState.gameEndReason);
setChess(new ChessEngine(gameState.state.position));
setPause(gameState.pause);
if (gameState.state.gameEndReason !== undefined) {
setGameInterruptedReason(gameState.state.gameEndReason);
}
return gameState;
return gameState.state;
});
},
false,
Expand Down Expand Up @@ -137,6 +147,7 @@ export function Game(): JSX.Element {
gameEndReason !== undefined ?
<GameEndDialog reason={gameEndReason} side={side} />
: null;

const gameOfferDialog =
gameHoldReason !== undefined ?
gameHoldReason === GameHoldReason.DRAW_CONFIRMATION ?
Expand All @@ -151,11 +162,25 @@ export function Game(): JSX.Element {
: null
: null;

const gamePauseDialog = paused ? <PauseDialog /> : null;

const gameUnpauseDialog =
gameHoldReason !== undefined ?
gameHoldReason === GameHoldReason.GAME_UNPAUSED ?
<NotificationDialog dialogText="Game Unpaused" />
: null
: null;

/** make moves by making a copy of the chessboard and sending the move message */
const handleMove = (move: Move): void => {
setChess(chess.copy(move));
sendMessage(new MoveMessage(move));
};
const handleMove =
!paused ?
(move: Move): void => {
setChess(chess.copy(move));
sendMessage(new MoveMessage(move));
}
: (move: Move): void => {
move;
}; //send a do-nothing function if game is paused
Comment on lines +182 to +183
Copy link

Copilot AI Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The paused move handler just references 'move' without doing anything. This should either be an empty function or have a comment explaining why the move parameter is referenced.

Suggested change
move;
}; //send a do-nothing function if game is paused
// Do nothing when the game is paused
};

Copilot uses AI. Check for mistakes.


// return the chessboard wrapper, navbar, and potential end dialog
return (
Expand All @@ -182,6 +207,8 @@ export function Game(): JSX.Element {
{gameEndDialog}
{gameOfferDialog}
{gameAcceptDialog}
{gamePauseDialog}
{gameUnpauseDialog}
<Outlet />
</div>
</>
Expand Down
2 changes: 2 additions & 0 deletions src/common/game-end-reasons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,6 @@ export enum GameHoldReason {
DRAW_OFFERED = "draw-offered",
DRAW_CONFIRMATION = "draw-confirmation",
DRAW_DENIED = "draw-denied",
GAME_PAUSED = "game-paused",
GAME_UNPAUSED = "game-unpaused",
}
81 changes: 45 additions & 36 deletions src/server/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { Side } from "../../common/game-types";
import { USE_VIRTUAL_ROBOTS, START_ROBOTS_AT_DEFAULT } from "../utils/env";
import { SaveManager } from "./save-manager";

import { VirtualBotTunnel, VirtualRobot } from "../simulator";
import { VirtualBotTunnel } from "../simulator";
import { Position } from "../robot/position";
import { DEGREE } from "../../common/units";
import { PacketType } from "../utils/tcp-packet";
Expand All @@ -50,15 +50,21 @@ import {
SpinRadiansCommand,
} from "../command/move-command";
import { GridIndices } from "../robot/grid-indices";
import { puzzles, type PuzzleComponents } from "./puzzles";
import {
moveAllRobotsHomeToDefaultOptimized,
moveAllRobotsToDefaultPositions,
moveAllRobotsFromBoardToHome,
} from "../robot/path-materializer";
import type { PuzzleComponents } from "./puzzles";
import { puzzles } from "./puzzles";
import { tcpServer } from "./tcp-interface";
import { robotManager } from "../robot/robot-manager";
import { executor } from "../command/executor";
import {
gamePaused,
pauseGame,
setAllRobotsToDefaultPositions,
unpauseGame,
} from "./pauseHandler";

/**
* Helper function to move all robots from their home positions to their default positions
Expand All @@ -74,7 +80,7 @@ async function setupDefaultRobotPositions(
moveAllRobotsToDefaultPositions(defaultPositionsMap);
await executor.execute(command);
} else {
setAllRobotsToDefaultPositions(defaultPositionsMap);
moveAllRobotsToDefaultPositions(defaultPositionsMap);
}
} else {
if (isMoving) {
Expand All @@ -86,26 +92,6 @@ async function setupDefaultRobotPositions(
}
}

function setAllRobotsToDefaultPositions(
defaultPositionsMap?: Map<string, GridIndices>,
): void {
if (defaultPositionsMap) {
for (const [robotId, indices] of defaultPositionsMap.entries()) {
const robot = robotManager.getRobot(robotId);
robot.position = Position.fromGridIndices(indices);
if (robot instanceof VirtualRobot)
robot.updateTunnelPosition(robot.position);
}
} else {
for (const robot of robotManager.idsToRobots.values()) {
robot.position = Position.fromGridIndices(robot.defaultIndices);
if (robot instanceof VirtualRobot)
robot.updateTunnelPosition(robot.position);
robotManager.updateRobot(robot.id, robot.defaultIndices);
}
}
}

/**
* An endpoint used to establish a websocket connection with the server.
*
Expand Down Expand Up @@ -184,6 +170,17 @@ apiRouter.get("/client-information", async (req, res) => {
),
);
}
const robotPos = new Map(
oldSave!.robotPos?.map<[string, GridIndices]>((obj) => [
obj[1],
new GridIndices(
parseInt(obj[0].split(", ")[0]),
parseInt(obj[0].split(", ")[1]),
),
]),
);
console.log(robotPos);
setAllRobotsToDefaultPositions(robotPos);
}
/**
* Note the client currently redirects to home from the game over screen
Expand All @@ -208,7 +205,10 @@ apiRouter.get("/game-state", (req, res) => {
return res.status(400).send({ message: "No game is currently active" });
}
const clientType = clientManager.getClientType(req.cookies.id);
return res.send(gameManager.getGameState(clientType));
return res.send({
state: gameManager.getGameState(clientType),
pause: gamePaused,
});
});

/**
Expand Down Expand Up @@ -283,7 +283,6 @@ apiRouter.post("/start-puzzle-game", async (req, res) => {
const fen = puzzle.fen;
const moves = puzzle.moves;
const difficulty = puzzle.rating;
const tooltip = puzzle.tooltip;

if (puzzle.robotDefaultPositions) {
// Convert puzzle.robotDefaultPositions from Record<string, string> to Map<string, GridIndices>
Expand Down Expand Up @@ -320,7 +319,7 @@ apiRouter.post("/start-puzzle-game", async (req, res) => {
new ChessEngine(),
socketManager,
fen,
tooltip,
"",
moves,
difficulty,
),
Expand All @@ -329,15 +328,6 @@ apiRouter.post("/start-puzzle-game", async (req, res) => {
return res.send({ message: "success" });
});

/**
* Returns robots to home after a game ends.
*/
apiRouter.post("/return-home", async (_req, res) => {
const command = moveAllRobotsFromBoardToHome();
await executor.execute(command);
return res.send({ message: "success" });
});

/**
* Returns all registered robot ids
*/
Expand Down Expand Up @@ -556,6 +546,25 @@ apiRouter.get("/get-puzzles", (_, res) => {
return res.send(out);
});

/**
* Pause the game
* Todo: add authentication instead of an exposed pause call
*/
apiRouter.get("/pause-game", (_, res) => {
return res.send(pauseGame(true));
});

/**
* Unpause the game
* Todo: add authentication instead of an exposed unpause call
*/

apiRouter.get("/unpause-game", async (_, res) => {
const unpausePacket = unpauseGame(true);

return res.send(unpausePacket);
});

/**
* sends a drive message through the tcp connection
*
Expand Down
Loading