diff --git a/src/client/game/admin-dialogs.tsx b/src/client/game/admin-dialogs.tsx index b5e6346f..bc050016 100644 --- a/src/client/game/admin-dialogs.tsx +++ b/src/client/game/admin-dialogs.tsx @@ -58,7 +58,7 @@ export function NotificationDialog(props: NotificationDialogProps) { className={buttonColor()} intent="primary" onClick={() => { - close(); + setIsOpen(false); }} /> ); diff --git a/src/client/game/game.tsx b/src/client/game/game.tsx index e4d4e466..c10d8741 100644 --- a/src/client/game/game.tsx +++ b/src/client/game/game.tsx @@ -104,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, @@ -161,12 +162,7 @@ export function Game(): JSX.Element { : null : null; - const gamePauseDialog = - gameHoldReason !== undefined ? - gameHoldReason === GameHoldReason.GAME_PAUSED ? - - : null - : null; + const gamePauseDialog = paused ? : null; const gameUnpauseDialog = gameHoldReason !== undefined ? @@ -177,7 +173,7 @@ export function Game(): JSX.Element { /** make moves by making a copy of the chessboard and sending the move message */ const handleMove = - paused ? + !paused ? (move: Move): void => { setChess(chess.copy(move)); sendMessage(new MoveMessage(move)); diff --git a/src/server/api/api.ts b/src/server/api/api.ts index 8f6d5705..11c016f1 100644 --- a/src/server/api/api.ts +++ b/src/server/api/api.ts @@ -52,15 +52,16 @@ 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 { GameHoldReason } from "../../common/game-end-reasons"; /** * Helper function to move all robots from their home positions to their default positions @@ -76,7 +77,7 @@ async function setupDefaultRobotPositions( moveAllRobotsToDefaultPositions(defaultPositionsMap); await executor.execute(command); } else { - setAllRobotsToDefaultPositions(defaultPositionsMap); + moveAllRobotsToDefaultPositions(defaultPositionsMap); } } else { if (isMoving) { @@ -186,6 +187,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 @@ -210,7 +222,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.flag, + }); }); /** @@ -285,7 +300,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 to Map @@ -322,7 +336,7 @@ apiRouter.post("/start-puzzle-game", async (req, res) => { new ChessEngine(), socketManager, fen, - tooltip, + "", moves, difficulty, ), @@ -331,15 +345,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 */ @@ -568,6 +573,35 @@ apiRouter.get("/pause-game", (_, res) => { /** * Unpause the game + * Todo: add authentication instead of an exposed unpause call + */ +apiRouter.get("/unpause-game", async (_, res) => { + if (gamePaused.flag) { + gamePaused.flag = false; + const ids = clientManager.getIds(); + if (ids) { + const oldSave = SaveManager.loadGame(ids[0]); + gameManager?.chess.loadFen(oldSave!.oldPos); + setAllRobotsToDefaultPositions( + new Map( + oldSave!.oldRobotPos?.map<[string, GridIndices]>((obj) => [ + obj[1], + new GridIndices( + parseInt(obj[0].split(", ")[0]), + parseInt(obj[0].split(", ")[1]), + ), + ]), + ), + ); + socketManager.sendToAll(new SetChessMessage(oldSave!.oldPos)); + } + socketManager.sendToAll( + new GameHoldMessage(GameHoldReason.GAME_UNPAUSED), + ); + return res.send({ message: "success" }); + } else { + return res.send({ message: "game not paused" }); + } * Resumes any leftover commands queued in the command executor * Todo: add authentication instead of an exposed unpause call */ diff --git a/src/server/api/game-manager.ts b/src/server/api/game-manager.ts index f357dda3..fc88ef27 100644 --- a/src/server/api/game-manager.ts +++ b/src/server/api/game-manager.ts @@ -26,6 +26,7 @@ import { SaveManager } from "./save-manager"; import { materializePath } from "../robot/path-materializer"; import { DO_SAVES } from "../utils/env"; import { executor } from "../command/executor"; +import { robotManager } from "../robot/robot-manager"; import { gamePaused } from "./managers"; type GameState = { @@ -164,6 +165,8 @@ export class HumanGameManager extends GameManager { this.hostSide, -1, this.chess.pgn, + this.chess.fen, + robotManager.getIndicesToIds(), ); } else { SaveManager.saveGame( @@ -172,6 +175,8 @@ export class HumanGameManager extends GameManager { oppositeSide(this.hostSide), -1, this.chess.pgn, + this.chess.fen, + robotManager.getIndicesToIds(), ); } } @@ -282,6 +287,8 @@ export class ComputerGameManager extends GameManager { this.hostSide, this.difficulty, this.chess.pgn, + this.chess.fen, + robotManager.getIndicesToIds(), ); } diff --git a/src/server/api/save-manager.ts b/src/server/api/save-manager.ts index 02f8ec59..570bd958 100644 --- a/src/server/api/save-manager.ts +++ b/src/server/api/save-manager.ts @@ -11,6 +11,7 @@ */ import { Side } from "../../common/game-types"; +2; // Save files contain a date in ms and pgn string export interface iSave { @@ -19,6 +20,10 @@ export interface iSave { hostWhite: boolean; aiDifficulty: number; game: string; + pos: string; + robotPos: Array<[string, string]>; + oldPos: string; + oldRobotPos: Array<[string, string]>; } export class SaveManager { @@ -27,9 +32,9 @@ export class SaveManager { * * Finds or creates a save file * Save name is host id + client id - * Saves include the game start time and save pgn as json - * - * Input: host id, client id, game pgn + * Saves include the game start time and save fen as json + * Robots are saved as a map of indices to robots + * Input: host id, client id, game fen, robots * Output: boolean competed */ public static saveGame( @@ -38,16 +43,28 @@ export class SaveManager { hostSide: Side, aiDiff: number, pgn: string, + fen: string, + robots: Map, ) { const day = new Date().getTime(); const side = hostSide === Side.WHITE; + console.log(JSON.stringify(robots)); const saveContents = { host: hostId, date: day, hostWhite: side, aiDifficulty: aiDiff, game: pgn, + pos: fen, + robotPos: Array.from(robots), + oldPos: "", + oldRobotPos: Array<[string, string]>(), }; + const oldGame = SaveManager.loadGame(hostId + "+" + clientID); + if (oldGame && oldGame.pos !== null) { + saveContents.oldPos = oldGame.pos; + saveContents.oldRobotPos = oldGame.robotPos; + } return FileManager.writeFile(hostId + "+" + clientID, saveContents); } diff --git a/src/server/command/command.ts b/src/server/command/command.ts index e955bada..6c8796c8 100644 --- a/src/server/command/command.ts +++ b/src/server/command/command.ts @@ -1,3 +1,4 @@ +import { gamePaused } from "../api/managers"; import { robotManager } from "../robot/robot-manager"; /** @@ -112,7 +113,9 @@ function isReversable(obj): obj is Reversible { */ export class ParallelCommandGroup extends CommandGroup { public async execute(): Promise { - const promises = this.commands.map((move) => move.execute()); + const promises = this.commands.map((move) => { + gamePaused.flag ? null : move.execute(); + }); return Promise.all(promises).then(null); } public async reverse(): Promise { @@ -132,7 +135,9 @@ export class SequentialCommandGroup extends CommandGroup { public async execute(): Promise { let promise = Promise.resolve(); for (const command of this.commands) { - promise = promise.then(() => command.execute()); + promise = promise.then(() => { + gamePaused.flag ? null : command.execute(); + }); } return promise; } diff --git a/src/server/robot/robot-manager.ts b/src/server/robot/robot-manager.ts index fd72e426..32daecfc 100644 --- a/src/server/robot/robot-manager.ts +++ b/src/server/robot/robot-manager.ts @@ -99,6 +99,12 @@ export class RobotManager { } indicesToIds.set(indices.toString(), robotId); } + + stopAllRobots() { + Array.from(this.idsToRobots.values()).forEach((robot) => { + robot.sendStopPacket(); + }); + } } export const robotManager = new RobotManager( diff --git a/src/server/robot/robot.ts b/src/server/robot/robot.ts index 23fb6fb1..548e28e0 100644 --- a/src/server/robot/robot.ts +++ b/src/server/robot/robot.ts @@ -193,4 +193,10 @@ export class Robot { timeDeltaMs: timeDeltaMs, }); } + + public async sendStopPacket(): Promise { + await this.tunnel!.send({ + type: PacketType.ESTOP, + }); + } }