Skip to content

Commit

Permalink
Add play against stockfish
Browse files Browse the repository at this point in the history
  • Loading branch information
jloh02 committed Jan 20, 2024
1 parent 0eeabfd commit 1ce3bc1
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 26 deletions.
2 changes: 2 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"socket.io": "^4.7.4",
"socketcluster-client": "^19.1.0",
"socketcluster-server": "^19.0.1",
"stockfish": "^10.0.2",
"uuid": "^9.0.1"
},
"keywords": [
Expand All @@ -43,6 +44,7 @@
"chess": "node test/illegalChess.js"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^20.11.5",
"socket.io-client": "^4.7.4",
"typescript": "^5.3.3"
Expand Down
106 changes: 82 additions & 24 deletions backend/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Chess } from "chess.js";
import "./utils/globals.js";
import { FailedMove, interpretMove } from "./chess/engine.js";
import { assertUnreachable } from "./utils/assertions.js";
import stockfish from "stockfish";

dotenv.config();

Expand All @@ -17,9 +18,16 @@ globalThis.roomFen = new Map();
const ENVIRONMENT = process.env.ENV || "dev";

const START_FEN = new Chess().fen();
const chess = new Chess();

const engine = stockfish();
engine.onmessage = function (msg) {
console.log(msg);
};
engine.postMessage("uci");

let expressApp = express();
expressApp.use(express.json());
expressApp.use(express.urlencoded({ extended: true }));
if (ENVIRONMENT === "dev") {
expressApp.use(morgan("dev"));
expressApp.use(cors());
Expand All @@ -40,6 +48,28 @@ expressApp.get("/health-check", (req, res) => {
res.status(200).send("OK");
});

expressApp.post("/stockfish", async (req, res) => {
console.log(req.body);
console.log("QUERY", req.body.fen);
// if chess engine replies
engine.onmessage = function (msg) {
console.log(msg);
// in case the response has already been sent?
if (res.headersSent) {
return;
}
// only send response when it is a recommendation
if (typeof (msg == "string") && msg.match("bestmove")) {
res.send(msg.split(" ")[1]);
}
};

// run chess engine
engine.postMessage("ucinewgame");
engine.postMessage("position fen " + req.body.fen);
engine.postMessage("go depth 20");
});

io.on("connection", (socket) => {
console.log(`${socket.id} connected`);

Expand All @@ -57,17 +87,22 @@ io.on("connection", (socket) => {

socket.on("join", (roomId, callback) => {
console.log(socket.id, "join:", roomId);
if (!io.sockets.adapter.rooms.has(roomId)) {
callback("denied");
if (roomId !== "ai" && !io.sockets.adapter.rooms.has(roomId)) {
callback(false);
return;
}
socket.join(roomId);
globalThis.roomFen.set(roomId, START_FEN);

if (io.sockets.adapter.rooms.get(roomId).size === 2)
io.to(roomId).emit("start", START_FEN, socket.id);
globalThis.roomFen.set(roomId === "ai" ? socket.id : roomId, START_FEN);

if (roomId === "ai" || io.sockets.adapter.rooms.get(roomId).size === 2)
io.to(roomId === "ai" ? socket.id : roomId).emit(
"start",
START_FEN,
socket.id
);

callback("answer");
callback(true);
});

socket.on("leave", () => {
Expand All @@ -78,41 +113,64 @@ io.on("connection", (socket) => {

socket.on("move", async (move, callback) => {
console.log(socket.id, move);
const allowed = Math.random() < 0.7; //TODO LLM

const roomIter = socket.rooms.values();
let roomId = "";
for (let room of roomIter) {
if (room !== socket.id) {
roomId = room;
break;
if (room === "ai") break;
}
}

// const allowed = Math.random() < 0.7;
// if (allowed) {
// const chess = new Chess(
// globalThis.roomFen.get(roomId === "ai" ? socket.id : roomId)
// );
// const availableMoves = chess.moves();
// chess.move(
// availableMoves[Math.floor(Math.random() * availableMoves.length)]
// );
// io.to(roomId === "ai" ? socket.id : roomId).emit(
// "update",
// chess.fen(),
// socket.id,
// move
// );
// globalThis.roomFen.set(roomId === "ai" ? socket.id : roomId, chess.fen());
// console.log(globalThis.roomFen);
// callback("Allowed " + move);
// } else callback("Denied");

const res = await interpretMove(move, globalThis.roomFen.get(roomId));
if (res instanceof FailedMove) {
callback(res.error);
} else if (typeof res === "string") {
io.to(roomId).emit("update", res, socket.id, move);
globalThis.roomFen.set(roomId, res);
callback(res);

if (roomId === "ai") {
fetch("http://localhost:8080/stockfish", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
fen: globalThis.roomFen.get(socket.id),
}),
}).then((response) =>
response.text().then((res) => {
const chess = new Chess(globalThis.roomFen.get(socket.id));
console.log(globalThis.roomFen.get(socket.id));
chess.move(res);
io.to(socket.id).emit("update", chess.fen(), "ai", res);
globalThis.roomFen.set(socket.id, chess.fen());
})
);
}
} else {
assertUnreachable(res);
}

// setTimeout(() => {
// if (allowed) {
// //TODO execute parsed move from LLM
// const availableMoves = chess.moves();
// chess.move(
// availableMoves[Math.floor(Math.random() * availableMoves.length)]
// );
// socket.rooms.forEach((roomId) => {
// if (roomId !== socket.id)
// io.to(roomId).emit("update", chess.fen(), socket.id, move);
// });
// callback("Allowed " + move);
// } else
// }, 1000);
});

socket.on("disconnecting", () => {
Expand Down
82 changes: 82 additions & 0 deletions backend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==

"@types/body-parser@*":
version "1.19.5"
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4"
integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==
dependencies:
"@types/connect" "*"
"@types/node" "*"

"@types/connect@*":
version "3.4.38"
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858"
integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==
dependencies:
"@types/node" "*"

"@types/cookie@^0.4.1":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d"
Expand All @@ -24,13 +39,75 @@
dependencies:
"@types/node" "*"

"@types/express-serve-static-core@^4.17.33":
version "4.17.41"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz#5077defa630c2e8d28aa9ffc2c01c157c305bef6"
integrity sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==
dependencies:
"@types/node" "*"
"@types/qs" "*"
"@types/range-parser" "*"
"@types/send" "*"

"@types/express@^4.17.21":
version "4.17.21"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d"
integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==
dependencies:
"@types/body-parser" "*"
"@types/express-serve-static-core" "^4.17.33"
"@types/qs" "*"
"@types/serve-static" "*"

"@types/http-errors@*":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f"
integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==

"@types/mime@*":
version "3.0.4"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.4.tgz#2198ac274de6017b44d941e00261d5bc6a0e0a45"
integrity sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==

"@types/mime@^1":
version "1.3.5"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690"
integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==

"@types/node@*", "@types/node@>=10.0.0", "@types/node@^20.11.5":
version "20.11.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.5.tgz#be10c622ca7fcaa3cf226cf80166abc31389d86e"
integrity sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==
dependencies:
undici-types "~5.26.4"

"@types/qs@*":
version "6.9.11"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.11.tgz#208d8a30bc507bd82e03ada29e4732ea46a6bbda"
integrity sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==

"@types/range-parser@*":
version "1.2.7"
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb"
integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==

"@types/send@*":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a"
integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==
dependencies:
"@types/mime" "^1"
"@types/node" "*"

"@types/serve-static@*":
version "1.15.5"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.5.tgz#15e67500ec40789a1e8c9defc2d32a896f05b033"
integrity sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==
dependencies:
"@types/http-errors" "*"
"@types/mime" "*"
"@types/node" "*"

abbrev@1:
version "1.1.1"
resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz"
Expand Down Expand Up @@ -1047,6 +1124,11 @@ [email protected]:
resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz"
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==

stockfish@^10.0.2:
version "10.0.2"
resolved "https://registry.yarnpkg.com/stockfish/-/stockfish-10.0.2.tgz#f4ba642f27e323b3adee9396d0ccfaf32553af18"
integrity sha512-MuDhChwRwFe4nkn5OKuDj2byf4lTVlpJK47D99kNi7Qr1QlM8PIi940AVdF4SMmFQnPtyYeT2xzlX69d5v8WQg==

stream-demux@^10.0.0, stream-demux@^10.0.1:
version "10.0.1"
resolved "https://registry.npmjs.org/stream-demux/-/stream-demux-10.0.1.tgz"
Expand Down
15 changes: 13 additions & 2 deletions frontend/src/Landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,25 @@ export default function Landing({
</>
) : (
<>
<Grid.Row>
<Button
primary
onClick={() => {
setConfirmedRoomCode("ai");
joinRoom("ai");
}}
>
Play Stockfish
</Button>
</Grid.Row>
<Grid.Row>
<Button
primary
onClick={async () => {
setConfirmedRoomCode(await createRoom());
}}
>
Create Game
Create Multiplayer Game
</Button>
</Grid.Row>
<Grid.Row>
Expand Down Expand Up @@ -93,7 +104,7 @@ export default function Landing({
></Input>
) : (
<Button onClick={() => setIsChoosingState(true)} primary>
Join Game
Join Multiplayer Game
</Button>
)}
</Grid.Row>
Expand Down
File renamed without changes.

0 comments on commit 1ce3bc1

Please sign in to comment.