BIG feat: add sockets, create and join games
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="/knight.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Chess Game</title>
|
||||
</head>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.14.2",
|
||||
"socket.io-client": "^4.7.1",
|
||||
"sort-by": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
Generated
+84
@@ -20,6 +20,9 @@ dependencies:
|
||||
react-router-dom:
|
||||
specifier: ^6.14.2
|
||||
version: 6.14.2(react-dom@18.2.0)(react@18.2.0)
|
||||
socket.io-client:
|
||||
specifier: ^4.7.1
|
||||
version: 4.7.1
|
||||
sort-by:
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0
|
||||
@@ -322,6 +325,10 @@ packages:
|
||||
engines: {node: '>=14'}
|
||||
dev: false
|
||||
|
||||
/@socket.io/component-emitter@3.1.0:
|
||||
resolution: {integrity: sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==}
|
||||
dev: false
|
||||
|
||||
/@swc/core-darwin-arm64@1.3.44:
|
||||
resolution: {integrity: sha512-Y+oVsCjXUPvr3D9YLuB1gjP84TseM/CRkbPNrf+3JXQhsPEkgxdIdFP1cl/obeqMQrRgPpvSfK+TOvGuOuV22g==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -569,6 +576,18 @@ packages:
|
||||
resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==}
|
||||
dev: true
|
||||
|
||||
/debug@4.3.4:
|
||||
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
dev: false
|
||||
|
||||
/didyoumean@1.2.2:
|
||||
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
||||
dev: true
|
||||
@@ -581,6 +600,25 @@ packages:
|
||||
resolution: {integrity: sha512-8rY8HdCxuSVY8wku3i/eDac4g1b4cSbruzocenrqBlzqruAZYHjQCHIjC66dLR9DXhEHTojsC4EjhZ8KmzwXqA==}
|
||||
dev: true
|
||||
|
||||
/engine.io-client@6.5.1:
|
||||
resolution: {integrity: sha512-hE5wKXH8Ru4L19MbM1GgYV/2Qo54JSMh1rlJbfpa40bEWkCKNo3ol2eOtGmowcr+ysgbI7+SGL+by42Q3pt/Ng==}
|
||||
dependencies:
|
||||
'@socket.io/component-emitter': 3.1.0
|
||||
debug: 4.3.4
|
||||
engine.io-parser: 5.1.0
|
||||
ws: 8.11.0
|
||||
xmlhttprequest-ssl: 2.0.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/engine.io-parser@5.1.0:
|
||||
resolution: {integrity: sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
dev: false
|
||||
|
||||
/esbuild@0.17.14:
|
||||
resolution: {integrity: sha512-vOO5XhmVj/1XQR9NQ1UPq6qvMYL7QFJU57J5fKBKBKxp17uDt5PgxFDb4A2nEiXhr1qQs4x0F5+66hVVw4ruNw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -800,6 +838,10 @@ packages:
|
||||
brace-expansion: 1.1.11
|
||||
dev: true
|
||||
|
||||
/ms@2.1.2:
|
||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||
dev: false
|
||||
|
||||
/mz@2.7.0:
|
||||
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
|
||||
dependencies:
|
||||
@@ -1055,6 +1097,30 @@ packages:
|
||||
loose-envify: 1.4.0
|
||||
dev: false
|
||||
|
||||
/socket.io-client@4.7.1:
|
||||
resolution: {integrity: sha512-Qk3Xj8ekbnzKu3faejo4wk2MzXA029XppiXtTF/PkbTg+fcwaTw1PlDrTrrrU4mKoYC4dvlApOnSeyLCKwek2w==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
dependencies:
|
||||
'@socket.io/component-emitter': 3.1.0
|
||||
debug: 4.3.4
|
||||
engine.io-client: 6.5.1
|
||||
socket.io-parser: 4.2.4
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/socket.io-parser@4.2.4:
|
||||
resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
dependencies:
|
||||
'@socket.io/component-emitter': 3.1.0
|
||||
debug: 4.3.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/sort-by@1.2.0:
|
||||
resolution: {integrity: sha512-aRyW65r3xMnf4nxJRluCg0H/woJpksU1dQxRtXYzau30sNBOmf5HACpDd9MZDhKh7ALQ5FgSOfMPwZEtUmMqcg==}
|
||||
dependencies:
|
||||
@@ -1199,6 +1265,24 @@ packages:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
dev: true
|
||||
|
||||
/ws@8.11.0:
|
||||
resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: ^5.0.2
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
dev: false
|
||||
|
||||
/xmlhttprequest-ssl@2.0.0:
|
||||
resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
dev: false
|
||||
|
||||
/yaml@2.3.1:
|
||||
resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg fill="#000000" height="800px" width="800px" version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 279.519 279.519" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 279.519 279.519">
|
||||
<g>
|
||||
<path d="m231.426,239.916h-13.5l-20.569-27.505 39.203-121.231c0.192-0.597 0.291-1.219 0.291-1.846 0-49.259-40.075-89.334-89.334-89.334-22.35,0-43.74,8.295-60.231,23.358-1.264,1.155-1.975,2.794-1.953,4.506 0.022,1.712 0.774,3.333 2.067,4.456l23.776,20.633-44.972,44.449c-6.649,6.574-6.915,17.127-0.604,24.025l14.575,15.93c1.606,1.757 4.101,2.395 6.357,1.631l49.715-16.894c1.061,0.677 2.151,1.305 3.274,1.873l-59.256,62.332c-13.653,14.363-19.268,34.451-15.305,53.617h-16.868c-3.313,0-6,2.687-6,6v27.603c0,3.313 2.687,6 6,6h183.334c3.313,0 6-2.687 6-6v-27.603c0-3.313-2.686-6-6-6zm-142.464-45.35l63.484-66.779c1.307,0.14 2.625,0.231 3.954,0.231 20.659,0 37.467-16.808 37.467-37.467 0-10.007-3.897-19.417-10.975-26.493-2.342-2.343-6.143-2.343-8.484,0-2.344,2.343-2.344,6.142 0,8.485 4.81,4.81 7.459,11.206 7.459,18.008 0,14.042-11.425,25.467-25.467,25.467-9.036,0-17.478-4.858-22.029-12.679-1.667-2.864-5.341-3.836-8.204-2.167-2.863,1.667-3.834,5.34-2.167,8.204 0.696,1.196 1.468,2.332 2.282,3.43l-39.899,13.559-11.93-13.039c-1.941-2.122-1.859-5.369 0.186-7.39l49.58-49.002c1.188-1.175 1.833-2.792 1.779-4.462-0.055-1.67-0.803-3.242-2.064-4.337l-23.354-20.267c13.434-10.275 29.863-15.868 46.937-15.868 42.332,0 76.83,34.188 77.328,76.404l-39.86,123.268c-0.599,1.853-0.262,3.88 0.904,5.439l17.054,22.804h-125.641c-4.2-16.056 0.153-33.242 11.66-45.349zm136.464,72.953h-171.334v-15.603h171.334v15.603z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -27,7 +27,7 @@ import Chess, {
|
||||
MOVE_FLAGS,
|
||||
PIECE_PROMOTION,
|
||||
PiecePromotionType,
|
||||
} from "../../../server/src/chess/engine";
|
||||
} from "../../../server/src/engine";
|
||||
import { MouseEventHandler, useState } from "react";
|
||||
import Show from "../utils/Show";
|
||||
import useWindowSize from "../utils/useWindowSize";
|
||||
@@ -38,10 +38,9 @@ const PIECES: Record<Color, Record<PieceType, string>> = {
|
||||
};
|
||||
const ChessBoard: React.FC<{
|
||||
chess: Chess;
|
||||
sendMove: (move: Move) => void;
|
||||
undo: () => void;
|
||||
makeMove: (move: Move) => void;
|
||||
blackPerspective?: boolean;
|
||||
}> = ({ chess, sendMove, undo, blackPerspective }) => {
|
||||
}> = ({ chess, makeMove, blackPerspective }) => {
|
||||
const { width, height } = useWindowSize();
|
||||
const [activeTile, setActiveTile] = useState<number>(-1);
|
||||
const [promotionMove, setPromotionMove] = useState<
|
||||
@@ -90,7 +89,7 @@ const ChessBoard: React.FC<{
|
||||
|
||||
if (tileProps[tile].isPromotion) return setPromotionMove(moveObj);
|
||||
|
||||
sendMove(moveObj);
|
||||
makeMove(moveObj);
|
||||
setActiveTile(-1);
|
||||
}
|
||||
|
||||
@@ -109,22 +108,16 @@ const ChessBoard: React.FC<{
|
||||
promotion,
|
||||
};
|
||||
|
||||
sendMove(moveObj);
|
||||
makeMove(moveObj);
|
||||
setPromotionMove(null);
|
||||
setActiveTile(-1);
|
||||
};
|
||||
|
||||
const handleUndo = () => {
|
||||
setPromotionMove(null);
|
||||
setActiveTile(-1);
|
||||
undo();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative w-fit h-fit isolate border-[6px] border-black rounded-lg peer">
|
||||
<div
|
||||
className="grid grid-rows-8 grid-cols-8 aspect-square"
|
||||
className="grid grid-rows-8 grid-cols-8 aspect-square select-none"
|
||||
onClick={handleClick}
|
||||
style={{ width: `${gridSize}px` }}
|
||||
>
|
||||
@@ -148,7 +141,6 @@ const ChessBoard: React.FC<{
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<button onClick={handleUndo}>undo</button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,3 +6,27 @@ img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.loading:after {
|
||||
display: inline-block;
|
||||
animation: dotty steps(1, end) 2s infinite;
|
||||
content: "";
|
||||
}
|
||||
|
||||
@keyframes dotty {
|
||||
0% {
|
||||
content: "";
|
||||
}
|
||||
25% {
|
||||
content: ".";
|
||||
}
|
||||
50% {
|
||||
content: "..";
|
||||
}
|
||||
75% {
|
||||
content: "...";
|
||||
}
|
||||
100% {
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
+13
-8
@@ -1,16 +1,21 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "./index.css";
|
||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||
import Home from "./routes/Home";
|
||||
import Game from "./routes/Game";
|
||||
|
||||
const router = createBrowserRouter([{
|
||||
path:'/',
|
||||
element:<Game />
|
||||
}]);
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <Home />,
|
||||
},
|
||||
{
|
||||
path: "/game",
|
||||
element: <Game />,
|
||||
},
|
||||
]);
|
||||
|
||||
// React's StricMode doesn't play well with how I implemented the socket connection
|
||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<RouterProvider router={router} />
|
||||
</React.StrictMode>
|
||||
<RouterProvider router={router} />
|
||||
);
|
||||
|
||||
+93
-15
@@ -1,26 +1,104 @@
|
||||
import Chess, { Move } from "../../../server/src/chess/engine";
|
||||
import { useState } from "react";
|
||||
import Chess, { COLOR, Move } from "../../../server/src/engine";
|
||||
import { useState, useEffect } from "react";
|
||||
import ChessBoard from "../components/Chessboard";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import useCopyToClipboard from "../utils/useCopyToClipboard";
|
||||
|
||||
import { socket } from "../sockets/socket";
|
||||
import { CopyIcon } from "../utils/icons";
|
||||
import Show from "../utils/Show";
|
||||
|
||||
const Game = () => {
|
||||
const navigate = useNavigate();
|
||||
const { state } = useLocation();
|
||||
|
||||
const { id, color } =
|
||||
state != null ? state : { id: undefined, color: undefined };
|
||||
|
||||
const App = () => {
|
||||
const [chess] = useState(Chess.load());
|
||||
const [, setUpdate] = useState(false);
|
||||
const [game, setGame] = useState(false);
|
||||
|
||||
const sendMove = (move: Move) => {
|
||||
const makeMove = (move: Move) => {
|
||||
socket.emit("make move", id, move);
|
||||
chess.makeMove(move);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log({ id, color });
|
||||
|
||||
if (id == undefined || color == undefined)
|
||||
return navigate("/", {
|
||||
state: { error: "Did not receive a game ID or color." },
|
||||
});
|
||||
|
||||
socket.connect();
|
||||
|
||||
socket.on("connect", () => {
|
||||
console.log("Connected to socket", socket.id);
|
||||
socket.emit("join game", id, color);
|
||||
});
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
console.log("Disconnected");
|
||||
});
|
||||
|
||||
socket.on("join error", () => {
|
||||
socket.disconnect();
|
||||
navigate("/", {
|
||||
state: { error: "Could not join, please try again." },
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("start game", () => setGame(true));
|
||||
|
||||
// TODO: board doesn't rerender
|
||||
socket.on("receive move", (move: Move) => {
|
||||
chess.makeMove(move);
|
||||
});
|
||||
|
||||
return () => {
|
||||
socket.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ChessBoard
|
||||
key={chess.getFEN()}
|
||||
chess={chess}
|
||||
sendMove={sendMove}
|
||||
undo={() => {
|
||||
chess.undo();
|
||||
setUpdate((prev) => !prev);
|
||||
}}
|
||||
/>
|
||||
<Show when={game} fallback={<Waiting id={id} />}>
|
||||
<ChessBoard
|
||||
key={chess.getFEN()}
|
||||
chess={chess}
|
||||
makeMove={makeMove}
|
||||
blackPerspective={color === COLOR.BLACK}
|
||||
/>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
const Waiting: React.FC<{
|
||||
id: string;
|
||||
}> = ({ id }) => {
|
||||
const [, copyToClipboard] = useCopyToClipboard();
|
||||
const [hasCopied, setHasCopied] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="ml-2">
|
||||
<div className="loading text-lg">Waiting for opponent to join</div>
|
||||
<div>
|
||||
Share this ID with your friend:
|
||||
<div className="bg-gray-800 text-white p-2 rounded-md w-fit">
|
||||
<code className="mr-4">{id}</code>
|
||||
<button
|
||||
className="inline-flex flex-row gap-1 justify-center items-center border border-white p-1 text-xs rounded-md"
|
||||
onClick={() => {
|
||||
copyToClipboard(id);
|
||||
setHasCopied(true);
|
||||
}}
|
||||
>
|
||||
<CopyIcon /> {hasCopied ? "Copied!" : "Copy"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Game;
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import { useState } from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { Color, COLOR } from "../../../server/src/engine";
|
||||
import Show from "../utils/Show";
|
||||
|
||||
const Home = () => {
|
||||
const navigate = useNavigate();
|
||||
const [id, setID] = useState<string>("");
|
||||
const [join, setJoin] = useState(false);
|
||||
const [color, setColor] = useState<Color>(COLOR.WHITE);
|
||||
const [error, setError] = useState<Error>();
|
||||
|
||||
const createGame = () => {
|
||||
fetch("/api/create-game")
|
||||
.then((res) => {
|
||||
if (res.ok) return res.text();
|
||||
throw res;
|
||||
})
|
||||
.then((id) => {
|
||||
setID(id);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
setError(error);
|
||||
});
|
||||
};
|
||||
|
||||
const joinGame = () => {
|
||||
fetch(`/api/other-color/${id}`)
|
||||
.then((res) => {
|
||||
if (res.ok) return res.text();
|
||||
throw res;
|
||||
})
|
||||
.then((text) => {
|
||||
if (text == "invalid id") throw new Error("Invalid game ID");
|
||||
if (text == "full") throw new Error("Game already had 2 players");
|
||||
|
||||
if (text == COLOR.BLACK)
|
||||
navigate("/game", { state: { id, color: text } });
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
setError(error);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Err />
|
||||
<div className="text-xl">
|
||||
<div className="absolute top-0 left-0 right-0 bottom-0 m-auto p-6 bg-gray-800 text-white w-fit h-fit rounded-[25px]">
|
||||
<button
|
||||
className="block cursor-pointer border-2 rounded-md p-2 w-full hover:bg-white hover:text-gray-800 transition-colors mb-3"
|
||||
onClick={createGame}
|
||||
>
|
||||
Create Game
|
||||
</button>
|
||||
<button
|
||||
className="block cursor-pointer border-2 rounded-md p-2 w-full hover:bg-white hover:text-gray-800 transition-colors"
|
||||
onClick={() => setJoin(true)}
|
||||
>
|
||||
Join Game
|
||||
</button>
|
||||
</div>
|
||||
<Show when={join == false && id != ""}>
|
||||
<div className="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex justify-center items-center bg-zinc-800 bg-opacity-70">
|
||||
<div className="bg-gray-900 text-white p-4 rounded-lg">
|
||||
<p>Select color:</p>
|
||||
<div className="grid grid-cols-[auto,minmax(0,1fr)] text-center ">
|
||||
<input
|
||||
type="radio"
|
||||
name="color"
|
||||
id="white"
|
||||
checked
|
||||
onChange={() => setColor(COLOR.WHITE)}
|
||||
/>
|
||||
<label htmlFor="white">White</label>
|
||||
<input
|
||||
type="radio"
|
||||
name="color"
|
||||
id="black"
|
||||
onChange={() => setColor(COLOR.BLACK)}
|
||||
/>
|
||||
<label htmlFor="black">Black</label>
|
||||
</div>
|
||||
<button
|
||||
className="block cursor-pointer border-2 rounded-md p-2 w-full hover:bg-white hover:text-gray-800 transition-colors mt-3"
|
||||
onClick={() => navigate("/game", { state: { id, color } })}
|
||||
>
|
||||
Start Game
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={join}>
|
||||
<div className="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex justify-center items-center bg-zinc-800 bg-opacity-70">
|
||||
<div className="bg-gray-900 text-white p-4 rounded-lg">
|
||||
<label htmlFor="game-id">Insert game ID: </label>
|
||||
<input
|
||||
className="block bg-zinc-700 p-1 mt-2"
|
||||
type="text"
|
||||
name="game-id"
|
||||
id="game-id"
|
||||
value={id}
|
||||
onChange={(e) => setID(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
className="block cursor-pointer border-2 rounded-md p-2 w-full hover:bg-white hover:text-gray-800 transition-colors mt-3"
|
||||
onClick={joinGame}
|
||||
>
|
||||
Join
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// FIX: currently not working
|
||||
const Err = () => {
|
||||
const { state } = useLocation();
|
||||
const error = state?.state;
|
||||
|
||||
return (
|
||||
<Show when={error != undefined}>
|
||||
<div>{error}</div>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
@@ -0,0 +1,9 @@
|
||||
import { io } from "socket.io-client";
|
||||
|
||||
// "undefined" means the URL will be computed from the `window.location` object
|
||||
const URL =
|
||||
process.env.NODE_ENV === "production" ? undefined : "http://localhost:5000";
|
||||
|
||||
export const socket = io(URL as string, {
|
||||
autoConnect: false,
|
||||
});
|
||||
@@ -3,7 +3,7 @@ const Show: React.FC<{
|
||||
children: React.ReactNode;
|
||||
fallback?: React.ReactNode;
|
||||
}> = ({ when, children, fallback }) => {
|
||||
return <> {when ? children : fallback}</>;
|
||||
return <> {when === true ? children : fallback ?? <></>}</>;
|
||||
};
|
||||
|
||||
export default Show;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
export const CopyIcon = () => (
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="currentColor"
|
||||
strokeWidth="0"
|
||||
viewBox="0 0 24 24"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path fill="none" d="M0 0h24v24H0V0z"></path>
|
||||
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"></path>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,43 @@
|
||||
import { useState, useCallback } from "react";
|
||||
|
||||
type State = {
|
||||
error: Error | null;
|
||||
text: string | null;
|
||||
};
|
||||
|
||||
export default function useCopyToClipboard(): [
|
||||
State,
|
||||
(value: any) => Promise<void>
|
||||
] {
|
||||
const [state, setState] = useState<State>({
|
||||
error: null,
|
||||
text: null,
|
||||
});
|
||||
|
||||
const copyToClipboard = useCallback(async (value: string) => {
|
||||
if (!navigator?.clipboard) {
|
||||
return setState({
|
||||
error: new Error("Clipboard not supported"),
|
||||
text: null,
|
||||
});
|
||||
}
|
||||
|
||||
const handleSuccess = () => {
|
||||
setState({
|
||||
error: null,
|
||||
text: value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleFailure = (e: any) => {
|
||||
setState({
|
||||
error: e,
|
||||
text: null,
|
||||
});
|
||||
};
|
||||
|
||||
navigator.clipboard.writeText(value).then(handleSuccess, handleFailure);
|
||||
}, []);
|
||||
|
||||
return [state, copyToClipboard];
|
||||
}
|
||||
@@ -12,5 +12,6 @@ export default defineConfig({
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
hmr: false,
|
||||
},
|
||||
});
|
||||
|
||||
+6
-5
@@ -5,11 +5,11 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node server/dist/server.js",
|
||||
"server": "nodemon server/src/server.ts",
|
||||
"client": "npm run dev --prefix client",
|
||||
"dev": "concurrently \"npm run server\" \"npm run client\"",
|
||||
"dev:server": "nodemon server/src/server.ts",
|
||||
"dev:client": "npm run dev --prefix client",
|
||||
"dev": "concurrently \"npm run dev:server\" \"npm run dev:client\"",
|
||||
"build:server": "tsc -p .",
|
||||
"build:client":"npm run build --prefix client",
|
||||
"build:client": "npm run build --prefix client",
|
||||
"build": "concurrently \"npm run build:server\" \"npm run build:client\"",
|
||||
"test": "vitest run"
|
||||
},
|
||||
@@ -18,7 +18,8 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2"
|
||||
"express": "^4.18.2",
|
||||
"socket.io": "^4.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.17",
|
||||
|
||||
Generated
+114
-3
@@ -11,6 +11,9 @@ dependencies:
|
||||
express:
|
||||
specifier: ^4.18.2
|
||||
version: 4.18.2
|
||||
socket.io:
|
||||
specifier: ^4.7.1
|
||||
version: 4.7.1
|
||||
|
||||
devDependencies:
|
||||
'@types/express':
|
||||
@@ -265,6 +268,10 @@ packages:
|
||||
'@jridgewell/sourcemap-codec': 1.4.14
|
||||
dev: true
|
||||
|
||||
/@socket.io/component-emitter@3.1.0:
|
||||
resolution: {integrity: sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==}
|
||||
dev: false
|
||||
|
||||
/@tsconfig/node10@1.0.9:
|
||||
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
|
||||
dev: true
|
||||
@@ -304,6 +311,16 @@ packages:
|
||||
'@types/node': 18.15.11
|
||||
dev: true
|
||||
|
||||
/@types/cookie@0.4.1:
|
||||
resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==}
|
||||
dev: false
|
||||
|
||||
/@types/cors@2.8.13:
|
||||
resolution: {integrity: sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==}
|
||||
dependencies:
|
||||
'@types/node': 18.15.11
|
||||
dev: false
|
||||
|
||||
/@types/express-serve-static-core@4.17.33:
|
||||
resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==}
|
||||
dependencies:
|
||||
@@ -327,7 +344,6 @@ packages:
|
||||
|
||||
/@types/node@18.15.11:
|
||||
resolution: {integrity: sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==}
|
||||
dev: true
|
||||
|
||||
/@types/qs@6.9.7:
|
||||
resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==}
|
||||
@@ -449,6 +465,11 @@ packages:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
dev: true
|
||||
|
||||
/base64id@2.0.0:
|
||||
resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
|
||||
engines: {node: ^4.5.0 || >= 5.9}
|
||||
dev: false
|
||||
|
||||
/binary-extensions@2.2.0:
|
||||
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -609,11 +630,24 @@ packages:
|
||||
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
|
||||
dev: false
|
||||
|
||||
/cookie@0.4.2:
|
||||
resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/cookie@0.5.0:
|
||||
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/cors@2.8.5:
|
||||
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
dependencies:
|
||||
object-assign: 4.1.1
|
||||
vary: 1.1.2
|
||||
dev: false
|
||||
|
||||
/create-require@1.1.1:
|
||||
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
||||
dev: true
|
||||
@@ -658,7 +692,6 @@ packages:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
dev: true
|
||||
|
||||
/deep-eql@4.1.3:
|
||||
resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
|
||||
@@ -713,6 +746,31 @@ packages:
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/engine.io-parser@5.1.0:
|
||||
resolution: {integrity: sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
dev: false
|
||||
|
||||
/engine.io@6.5.1:
|
||||
resolution: {integrity: sha512-mGqhI+D7YxS9KJMppR6Iuo37Ed3abhU8NdfgSvJSDUafQutrN+sPTncJYTyM9+tkhSmWodKtVYGPPHyXJEwEQA==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
dependencies:
|
||||
'@types/cookie': 0.4.1
|
||||
'@types/cors': 2.8.13
|
||||
'@types/node': 18.15.11
|
||||
accepts: 1.3.8
|
||||
base64id: 2.0.0
|
||||
cookie: 0.4.2
|
||||
cors: 2.8.5
|
||||
debug: 4.3.4
|
||||
engine.io-parser: 5.1.0
|
||||
ws: 8.11.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/esbuild@0.17.15:
|
||||
resolution: {integrity: sha512-LBUV2VsUIc/iD9ME75qhT4aJj0r75abCVS0jakhFzOtR7TQsqQA5w0tZ+KTKnwl3kXE0MhskNdHDh/I5aCR1Zw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -1031,7 +1089,6 @@ packages:
|
||||
|
||||
/ms@2.1.2:
|
||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||
dev: true
|
||||
|
||||
/ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
@@ -1076,6 +1133,11 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/object-assign@4.1.1:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/object-inspect@1.12.3:
|
||||
resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==}
|
||||
dev: false
|
||||
@@ -1313,6 +1375,42 @@ packages:
|
||||
is-fullwidth-code-point: 4.0.0
|
||||
dev: true
|
||||
|
||||
/socket.io-adapter@2.5.2:
|
||||
resolution: {integrity: sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==}
|
||||
dependencies:
|
||||
ws: 8.11.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/socket.io-parser@4.2.4:
|
||||
resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
dependencies:
|
||||
'@socket.io/component-emitter': 3.1.0
|
||||
debug: 4.3.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/socket.io@4.7.1:
|
||||
resolution: {integrity: sha512-W+utHys2w//dhFjy7iQQu9sGd3eokCjGbl2r59tyLqNiJJBdIebn3GAKEXBr3osqHTObJi2die/25bCx2zsaaw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
dependencies:
|
||||
accepts: 1.3.8
|
||||
base64id: 2.0.0
|
||||
cors: 2.8.5
|
||||
debug: 4.3.4
|
||||
engine.io: 6.5.1
|
||||
socket.io-adapter: 2.5.2
|
||||
socket.io-parser: 4.2.4
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/source-map-js@1.0.2:
|
||||
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -1660,6 +1758,19 @@ packages:
|
||||
strip-ansi: 6.0.1
|
||||
dev: true
|
||||
|
||||
/ws@8.11.0:
|
||||
resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: ^5.0.2
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
dev: false
|
||||
|
||||
/y18n@5.0.8:
|
||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect, test } from "vitest";
|
||||
import Chess, { DEFAULT_POSITION } from "../chess/engine";
|
||||
import Chess, { DEFAULT_POSITION } from "../engine";
|
||||
|
||||
test("starting position", () => {
|
||||
expect(Chess.load().getFEN()).toEqual(DEFAULT_POSITION);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import Chess, { InternalMove, MOVE_FLAGS, Square } from "../chess/engine";
|
||||
import Chess, { InternalMove, MOVE_FLAGS, Square } from "../engine";
|
||||
|
||||
type ExpectedMoves = Record<
|
||||
string,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import Chess from "../chess/engine";
|
||||
import Chess from "../engine";
|
||||
|
||||
describe("insufficient material", () => {
|
||||
test("2 kings", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect, test } from "vitest";
|
||||
import Chess from "../chess/engine";
|
||||
import Chess from "../engine";
|
||||
|
||||
test("", () => {
|
||||
const chess = Chess.load();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import Chess, { COLOR, PIECE } from "../chess/engine";
|
||||
import Chess, { COLOR, PIECE } from "../engine";
|
||||
|
||||
describe("valid FEN strings", () => {
|
||||
test("default", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { DEFAULT_POSITION, validateFEN } from "../chess/engine";
|
||||
import { DEFAULT_POSITION, validateFEN } from "../engine";
|
||||
|
||||
describe("valid FEN strings", () => {
|
||||
test("starting position", () => {
|
||||
|
||||
@@ -985,6 +985,10 @@ export default class Chess {
|
||||
// TODO: implement
|
||||
history() { }
|
||||
|
||||
toString() {
|
||||
return this.getFEN();
|
||||
}
|
||||
|
||||
static Builder = class {
|
||||
private _board: Board = new Array(64).fill(null);
|
||||
private _turn: Color = COLOR.WHITE;
|
||||
+106
-3
@@ -1,12 +1,115 @@
|
||||
import express, { Application, Request, Response } from "express";
|
||||
import express, { Request, Response } from "express";
|
||||
import path from "path";
|
||||
import dotenv from "dotenv";
|
||||
import http from "http";
|
||||
import { Server } from "socket.io";
|
||||
import { randomUUID } from "crypto";
|
||||
import Chess, { COLOR, Color, Move } from "./engine";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const PORT = process.env.PORT || 5000;
|
||||
|
||||
const app: Application = express();
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const io = new Server(
|
||||
server,
|
||||
process.env.NODE_ENV === "development"
|
||||
? {
|
||||
cors: {
|
||||
origin: "http://localhost:3000",
|
||||
},
|
||||
}
|
||||
: {}
|
||||
);
|
||||
|
||||
type Room = {
|
||||
[color in Color]: string | null;
|
||||
} & {
|
||||
game: Chess;
|
||||
};
|
||||
|
||||
const rooms: Map<string, Room> = new Map();
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
console.log("A user connected", socket.id);
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
console.log("A user disconnected", socket.id);
|
||||
});
|
||||
|
||||
socket.on("join game", (id: string, color: Color) => {
|
||||
const room = rooms.get(id);
|
||||
|
||||
console.log({
|
||||
isRoom: room != undefined,
|
||||
w: room?.w,
|
||||
b: room?.b,
|
||||
sock: socket.id,
|
||||
color,
|
||||
cond:
|
||||
room == undefined || (room[color] != null && room[color] != socket.id),
|
||||
});
|
||||
|
||||
if (
|
||||
room == undefined ||
|
||||
(room[color] != null && room[color] != socket.id)
|
||||
) {
|
||||
socket.emit("join error");
|
||||
return;
|
||||
}
|
||||
|
||||
socket.join(id);
|
||||
|
||||
room[color] = socket.id;
|
||||
|
||||
if (room[COLOR.WHITE] != null && room[COLOR.BLACK] != null)
|
||||
io.to(id).emit("start game");
|
||||
});
|
||||
|
||||
socket.on("make move", (id: string, move: Move) => {
|
||||
socket.to(id).emit("receive move", move);
|
||||
});
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV === "development")
|
||||
app.get("/games", (_req, res) => {
|
||||
const m = new Map();
|
||||
rooms.forEach((room, id) =>
|
||||
m.set(id, {
|
||||
w: room.w,
|
||||
b: room.b,
|
||||
game: room.game.getFEN(),
|
||||
})
|
||||
);
|
||||
return res.json(Object.fromEntries(m));
|
||||
});
|
||||
|
||||
app.get("/api/create-game", (_req, res) => {
|
||||
let id = randomUUID();
|
||||
|
||||
while (rooms.has(id)) id = randomUUID();
|
||||
|
||||
rooms.set(id, {
|
||||
b: null,
|
||||
w: null,
|
||||
game: Chess.load(),
|
||||
});
|
||||
|
||||
res.send(id);
|
||||
console.log("Created room:", id);
|
||||
});
|
||||
|
||||
app.get("/api/other-color/:id", (req, res) => {
|
||||
const id = req.params.id;
|
||||
|
||||
const room = rooms.get(id);
|
||||
|
||||
if (room == undefined) res.send("invalid id");
|
||||
else if (room[COLOR.WHITE] == null) res.send(COLOR.WHITE);
|
||||
else if (room[COLOR.BLACK] == null) res.send(COLOR.BLACK);
|
||||
else res.send("full");
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
const __dirname = path.resolve();
|
||||
@@ -33,6 +136,6 @@ app.use((err: Error, _req: Request, res: Response) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(PORT, () =>
|
||||
server.listen(PORT, () =>
|
||||
console.log("Server listening on http://localhost:" + PORT)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user