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,
+ });
+ }
}