Play with Computer
This is the content for the Play with Computer tab.
;
+ return Recordings
This is the content for the Recordings tab.
;
default:
@@ -430,24 +431,24 @@ const NewStudentProfile = ({ userPortraitSrc }: any) => {
{tabKeys.map((tab) => {
// Map tab names to icon names (prodev -> learning, mathLessons -> learning)
- const iconName = tab === "prodev"
- ? "learningIcon"
- : tab === "mathLessons"
+ const iconName = tab === "prodev"
? "learningIcon"
- : `${tab}Icon`;
-
+ : tab === "mathLessons"
+ ? "learningIcon"
+ : `${tab}Icon`;
+
// Create display name for the tab
const displayName =
tab === "chessLessons"
? "Chess Lessons"
: tab === "mathLessons"
- ? "Math Lessons"
- : tab === "playComputer"
- ? "Play with Computer"
- : tab === "prodev"
- ? "Learning"
- : tab.charAt(0).toUpperCase() + tab.slice(1);
-
+ ? "Math Lessons"
+ : tab === "playComputer"
+ ? "Play with Computer"
+ : tab === "prodev"
+ ? "Learning"
+ : tab.charAt(0).toUpperCase() + tab.slice(1);
+
return (
{
- return res.json({ status: "OK" });
-});
-
-/**
- * Handle new Socket.IO client connections
- * Initializes Stockfish session for each connected client
- */
-io.on("connection", (socket) => {
- console.log(`New client (${socket.id}) connected!`);
- initializeSocket(io, socket);
-});
-
-// Start the server on specified port
-const PORT = process.env.PORT || 5001;
-server.listen(PORT, () => {
- console.log(`Stockfish server running on port ${PORT}`);
-});
\ No newline at end of file
diff --git a/stockfishServer/src/index.js b/stockfishServer/src/index.js
index 5642db24..44e60989 100644
--- a/stockfishServer/src/index.js
+++ b/stockfishServer/src/index.js
@@ -1,98 +1,35 @@
-// Load environment variables from .env file
require("dotenv").config();
const express = require("express");
-const rateLimit = require("express-rate-limit");
-const { Chess } = require("chess.js");
-const Stockfish = require("stockfish");
-const querystring = require("querystring");
-const url = require("url");
-const { SSL_OP_SSLEAY_080_CLIENT_DH_BUG } = require("constants");
+const http = require("http");
+const { Server } = require("socket.io");
+const cors = require("cors");
+const initializeSocket = require("./managers/socket");
const app = express();
+const server = http.createServer(app);
-// Set rate limit - 30 requests/minute per IP
-const limiter = rateLimit({
- windowMs: 60 * 1000,
- max: 30,
- message: { error: "Too many requests! Please try again." },
+const io = new Server(server, {
+ cors: {
+ origin: "*",
+ methods: ["GET", "POST"],
+ },
});
-app.use(limiter);
-// CORS headers
-app.use((req, res, next) => {
- // WARNING: allow only selected access for production
- res.setHeader("Access-Control-Allow-Origin", "*");
+app.use(cors());
+app.use(express.json());
- res.setHeader("Content-Type", "application/json");
- next();
+app.get("/health", (req, res) => {
+ res.json({ status: "ok" });
});
-/**
- * Main API route - evaluates chess positions using Stockfish engine
- * Accepts FEN string, optional move, difficulty level, and info flag
- */
-app.get("/", (req, res) => {
- // Parse query parameters from the URL
- const params = querystring.parse(url.parse(req.url, true).search?.substring(1));
- const { fen, move = "", level = 10, info = false } = params;
-
- // Validate input
- if (!fen || isNaN(level)) {
- return res.status(400).json({ error: "Missing or invalid parameters!" });
- }
-
- // Set maximum depth level and create engine instance
- const maxLevel = 30;
- var lines = [];
- var depth = Math.min(parseInt(level), maxLevel);
- const engine = Stockfish();
-
- // Create a new chess game from the provided FEN position
- const game = new Chess(fen);
-
- // Handle messages from the Stockfish engine
- engine.onmessage = (line) => {
- // If info mode is requested, collect all engine output lines
- if (info) {
-
- lines.push(line);
- if (line.startsWith("bestmove")) {
- res.json({ output: lines });
- }
- }
- // Otherwise, return only the best move with details
- else if (line.startsWith("bestmove")) {
- const moveResult = game.move(line.split(" ")[1], { sloppy: true });
-
- if (moveResult) {
- const color = moveResult.color;
- const piece = moveResult.piece.toUpperCase();
- const move = color + piece;
- const target = moveResult.to;
- res.end(`${game.fen()} move:${move} target:${target}`);
- }
- else {
- res.end("Invalid move or game state");
- }
- }
- }
-
- // Send position + evaluation command to the engine
- engine.postMessage(`position fen ${fen} moves ${move}`);
- engine.postMessage(`go depth ${depth}`);
-
- // Set timeout for safety
- setTimeout(() => {
- if (!res.writableEnded) {
- res.status(504).json({ error: "Engine timeout!" });
- }
- }, 5000);
+io.on("connection", (socket) => {
+ console.log(`Client connected ${socket.id}`);
+ initializeSocket(socket);
});
+const PORT = process.env.PORT || 8080;
-// Start the server
-const PORT = process.env.PORT || 3002;
-app.listen(PORT, () => {
+server.listen(PORT, () => {
console.log(`Stockfish server running on port ${PORT}`);
});
diff --git a/stockfishServer/src/managers/StockfishManager.js b/stockfishServer/src/managers/StockfishManager.js
index 538166e7..acfa7165 100644
--- a/stockfishServer/src/managers/StockfishManager.js
+++ b/stockfishServer/src/managers/StockfishManager.js
@@ -1,19 +1,20 @@
const { spawn } = require('child_process');
const crypto = require("crypto");
const { Chess } = require("chess.js");
+const path = require("path");
// Determine the correct Stockfish binary path based on platform
let enginePath;
switch (process.platform) {
case 'win32':
- enginePath = "./bin/stockfish_11_win.exe";
+ enginePath = path.join(__dirname, "..", "bin", "stockfish_11_win.exe");
break;
case 'darwin':
- enginePath = "./bin/stockfish_11_mac";
+ enginePath = path.join(__dirname, "..", "bin", "stockfish_11_mac");
break;
case 'linux':
- enginePath = "./bin/stockfish_11_linux";
+ enginePath = path.join(__dirname, "..", "bin", "stockfish_11_linux");
break;
default:
throw new Error(`Unsupported platform: ${process.platform}`);
diff --git a/stockfishServer/src/managers/socket.js b/stockfishServer/src/managers/socket.js
index ebb76e18..d7c325ce 100644
--- a/stockfishServer/src/managers/socket.js
+++ b/stockfishServer/src/managers/socket.js
@@ -6,7 +6,7 @@ const stockfishManager = new StockfishManager();
* @param {Server} io - Socket.IO server instance
* @param {Socket} socket - Connected socket instance
*/
-const initializeSocket = (io, socket) => {
+const initializeSocket = (socket) => {
// Start a new Stockfish session for the client
socket.on("start-session", ({ sessionType, fen }) => {
try {
@@ -35,6 +35,16 @@ const initializeSocket = (io, socket) => {
}
});
+ // End the current session without disconnecting the socket
+ socket.on("end-session", () => {
+ try {
+ stockfishManager.deleteSession(socket.id);
+ socket.emit("session-ended", { success: true });
+ } catch (err) {
+ console.error("Error ending session:", err);
+ }
+ });
+
// Clean up session when client disconnects
socket.on("disconnect", () => {
stockfishManager.deleteSession(socket.id);
diff --git a/stockfishServer/src/tests/index.test.js b/stockfishServer/src/tests/index.test.js
index 9f6d3b29..70075f5d 100644
--- a/stockfishServer/src/tests/index.test.js
+++ b/stockfishServer/src/tests/index.test.js
@@ -1,6 +1,7 @@
const ioClient = require("socket.io-client");
const http = require("http");
const express = require("express");
+const { Server } = require("socket.io");
const socketHandler = require("../managers/socket");
describe("Server and socket", () => {
@@ -12,18 +13,19 @@ describe("Server and socket", () => {
const app = express();
server = http.createServer(app);
- ioServer = require("socket.io")(server, {
+ ioServer = new Server(server, {
cors: { origin: "*", methods: ["GET", "POST"] },
});
ioServer.on("connection", (socket) => {
- socketHandler(ioServer, socket);
+ socketHandler(socket);
});
- stockfishManager.registerSession = jest.fn();
- stockfishManager.updateFen = jest.fn();
- stockfishManager.evaluateFen = jest.fn();
- stockfishManager.deleteSession = jest.fn();
+ // Mock the stockfishManager methods
+ jest.spyOn(stockfishManager, 'registerSession').mockReturnValue(undefined);
+ jest.spyOn(stockfishManager, 'updateFen').mockReturnValue(undefined);
+ jest.spyOn(stockfishManager, 'evaluateFen').mockReturnValue(undefined);
+ jest.spyOn(stockfishManager, 'deleteSession').mockReturnValue(undefined);
server.listen(() => {
const port = server.address().port;
@@ -37,6 +39,8 @@ describe("Server and socket", () => {
});
afterEach((done) => {
+ jest.restoreAllMocks();
+
if (clientSocket.connected) {
clientSocket.disconnect();
}
@@ -49,11 +53,11 @@ describe("Server and socket", () => {
clientSocket.on("session-started", (data) => {
expect(data.success).toBe(true);
- serverSocketId = data;
+ serverSocketId = data.id;
expect(stockfishManager.registerSession).toHaveBeenCalled();
done();
});
- });
+ }, 10000);
test("start-session emits session-error on failure", (done) => {
stockfishManager.registerSession.mockImplementation(() => {
@@ -66,7 +70,7 @@ describe("Server and socket", () => {
expect(data.error).toBe("Session failed");
done();
});
- });
+ }, 10000);
test("update-fen calls updateFen", (done) => {
const fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1";
@@ -84,9 +88,9 @@ describe("Server and socket", () => {
fen
);
done();
- }, 50);
+ }, 100);
});
- });
+ }, 10000);
test("evaluate-fen calls evaluateFen", (done) => {
const fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1";
@@ -108,9 +112,9 @@ describe("Server and socket", () => {
level
);
done();
- }, 50);
+ }, 100);
});
- });
+ }, 10000);
test("disconnect triggers deleteSession", (done) => {
clientSocket.emit("start-session", { sessionType: "lesson", fen: "" });
@@ -125,7 +129,7 @@ describe("Server and socket", () => {
serverSocketId
);
done();
- }, 50);
+ }, 100);
});
- });
+ }, 10000);
});