Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
Oceankoh committed Jan 20, 2024
2 parents 00f3575 + 0f34e6c commit c940b8d
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 148 deletions.
291 changes: 153 additions & 138 deletions backend/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createServer } from "http";
import express from "express";
import morgan from "morgan";
import cors from "cors";
import { generateRoomCode } from "./utils/calc.js";
import { generateRoomCode, processGameover } from "./utils/calc.js";
import dotenv from "dotenv";
import { Server } from "socket.io";
import { Chess } from "chess.js";
Expand All @@ -17,169 +17,184 @@ globalThis.roomFen = new Map();

const ENVIRONMENT = process.env.ENV || "dev";

const START_FEN = new Chess().fen();
const START_FEN =
"rnbqkbnr/ppppp2p/5p2/6p1/8/4P1P1/PPPP1P1P/RNBQKBNR w KQkq g6 0 3"; //new Chess().fen();

const engine = stockfish();
engine.onmessage = function (msg) {
console.log(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());
expressApp.use(morgan("dev"));
expressApp.use(cors());
}

let httpServer = createServer(expressApp);

const io = new Server(httpServer, {
serveClient: false,
cors:
ENVIRONMENT === "dev"
? { origin: "*", methods: ["GET", "POST"] }
: undefined,
serveClient: false,
cors:
ENVIRONMENT === "dev"
? { origin: "*", methods: ["GET", "POST"] }
: undefined,
});

// Add GET /health-check express route
expressApp.get("/health-check", (req, res) => {
res.status(200).send("OK");
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");
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`);

socket.on("ping", (data, ack) => {
const startTime = Date.now();
ack({ data: "pong", timestamp: data.timestamp });
});

socket.on("create", (callback) => {
const roomId = generateRoomCode();
console.log(socket.id, "create:", roomId);
socket.join(roomId);
callback(roomId);
});

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

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(true);
});

socket.on("leave", () => {
socket.rooms.forEach((roomId) => {
if (roomId !== socket.id) socket.leave(roomId);
console.log(`${socket.id} connected`);

socket.on("ping", (data, ack) => {
const startTime = Date.now();
ack({ data: "pong", timestamp: data.timestamp });
});

socket.on("create", (callback) => {
const roomId = generateRoomCode();
console.log(socket.id, "create:", roomId);
socket.join(roomId);
callback(roomId);
});

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

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(true);
});

socket.on("leave", () => {
socket.rooms.forEach((roomId) => {
if (roomId !== socket.id) socket.leave(roomId);
});
});

socket.on("move", async (move, callback) => {
console.log(socket.id, move);

const roomIter = socket.rooms.values();
let roomId = "";
for (let room of roomIter) {
if (room !== socket.id) {
roomId = room;
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 (res instanceof SuccessfulMove) {
io.to(roomId === "ai" ? socket.id : roomId).emit(
"update",
res,
socket.id,
res.move.toString()
);
globalThis.roomFen.set(roomId, res.fen);
callback(res.move.toString());

processGameover(
new Chess(res.fen),
io,
roomId === "ai" ? socket.id : roomId
);

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());

processGameover(chess, io, socket.id);
})
);
}
} else {
assertUnreachable(res);
}
});

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

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

socket.on("move", async (move, callback) => {
console.log(socket.id, move);

const roomIter = socket.rooms.values();
let roomId = "";
for (let room of roomIter) {
if (room !== socket.id) {
roomId = room;
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 (res instanceof SuccessfulMove) {
io.to(roomId).emit("update", res.fen, socket.id, move);
globalThis.roomFen.set(roomId, res.fen);
callback(res.move.toString());

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

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

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

httpServer.listen(8080);
13 changes: 13 additions & 0 deletions backend/src/utils/calc.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Chess } from "chess.js";
import { Server } from "socket.io";

// Generate a random 6-letter room code
export function generateRoomCode() {
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
Expand All @@ -9,3 +12,13 @@ export function generateRoomCode() {
}
return roomCode;
}

export function processGameover(chess: Chess, io: Server, roomId: string) {
if (chess.isGameOver()) io.to(roomId).emit("gameover", getWinner(chess));
}

function getWinner(chess: Chess) {
if (chess.isCheckmate())
return chess.turn() === "b" ? "White wins!" : "Black wins!";
else return "Draw!";
}
18 changes: 8 additions & 10 deletions frontend/src/Game.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
import {
Button,
Container,
Grid,
Icon,
Input,
Loader,
} from "semantic-ui-react";
import { Button, Container, Grid, Icon, Input } from "semantic-ui-react";
import { Chessboard } from "react-chessboard";
import "./Game.css";
import { Message } from "./types/message";
import { useCallback, useEffect, useRef, useState } from "react";
import { move, onUpdate } from "./utils/socket";
import { move, onGameover, onUpdate } from "./utils/socket";

export default function Game({
fen,
Expand All @@ -25,6 +18,7 @@ export default function Game({
const [text, setText] = useState("");
const [messages, setMessages] = useState<Message[]>([]);
const [isLoading, setLoading] = useState(false);
const [gameover, setGameover] = useState(false);

// const messages: Message[] = [
// { outgoing: true, text: "Knight to E5" },
Expand All @@ -51,6 +45,10 @@ export default function Game({
msgs.concat({ outgoing: false, text: "Your turn!" })
);
});
onGameover((text) => {
setMessages((msgs) => msgs.concat({ outgoing: false, text }));
setGameover(true);
});
}, []);

const sendMessage = useCallback(() => {
Expand Down Expand Up @@ -127,7 +125,7 @@ export default function Game({
? "Enter move"
: "Waiting for opponent..."
}
disabled={!isTurn || isLoading}
disabled={!isTurn || isLoading || gameover}
/>
</Container>
</div>
Expand Down
Loading

0 comments on commit c940b8d

Please sign in to comment.