diff --git a/client/postcss.config.js b/client/postcss.config.js index 2e7af2b..2aa7205 100644 --- a/client/postcss.config.js +++ b/client/postcss.config.js @@ -3,4 +3,4 @@ export default { tailwindcss: {}, autoprefixer: {}, }, -} +}; diff --git a/client/src/components/ChessUI.tsx b/client/src/components/ChessUI.tsx index 82c27f7..1d5612a 100644 --- a/client/src/components/ChessUI.tsx +++ b/client/src/components/ChessUI.tsx @@ -8,7 +8,7 @@ import Modal, { ModalButton } from "./Modal"; import { useState } from "react"; const ChessUI: React.FC> = ( - props + props, ) => { const [showModal, setShowModal] = useState(true); const navigate = useNavigate(); diff --git a/client/src/components/Chessboard.tsx b/client/src/components/Chessboard.tsx index bddeefa..4d72e10 100644 --- a/client/src/components/Chessboard.tsx +++ b/client/src/components/Chessboard.tsx @@ -121,7 +121,7 @@ const ChessBoard: React.FC<{ if (tile == activeTile) return setActiveTile(-1); setActiveTile( - chess.getMovesForSquare(algebraic(tile)).length == 0 ? -1 : tile + chess.getMovesForSquare(algebraic(tile)).length == 0 ? -1 : tile, ); }; @@ -198,60 +198,62 @@ const Tile: React.FC<{ blackPerspective, endGameIcon, }) => { - const color = squareColor(tileNumber); + const color = squareColor(tileNumber); - const { bg: bgColor, text: textColor } = TILE_COLORS[color]; - const activeColor = TILE_COLORS[color].active; + const { bg: bgColor, text: textColor } = TILE_COLORS[color]; + const activeColor = TILE_COLORS[color].active; - const tileFile = file(tileNumber); - const tileRank = rank(tileNumber); - const square = algebraic(tileNumber); + const tileFile = file(tileNumber); + const tileRank = rank(tileNumber); + const square = algebraic(tileNumber); - const isFirstColumn = tileFile == (blackPerspective ? FILE.H : FILE.A); - const isLastRow = tileRank == (blackPerspective ? RANK.EIGHTH : RANK.FIRST); + const isFirstColumn = tileFile == (blackPerspective ? FILE.H : FILE.A); + const isLastRow = tileRank == (blackPerspective ? RANK.EIGHTH : RANK.FIRST); - return ( -
*]:pointer-events-none outline-blue-300 hover:outline outline-2 sm:outline-4 -outline-offset-2 sm:-outline-offset-4", - isActive ? activeColor : bgColor, - piece ? "cursor-pointer" : "", - ].join(" ")} - data-tile={tileNumber} - > - {piece && ( - - )} - -
- {endGameIcon} -
+ if (isFirstColumn || isLastRow) console.log(square); + + return ( +
*]:pointer-events-none outline-blue-300 hover:outline outline-2 sm:outline-4 -outline-offset-2 sm:-outline-offset-4", + isActive ? activeColor : bgColor, + piece ? "cursor-pointer" : "", + ].join(" ")} + data-tile={tileNumber} + > + {piece && ( + + )} + +
+ {endGameIcon} +
+
+ +
+ } + > +
- -
- } - > -
- - - -
- {square[1]} -
-
- -
- {square[0]} -
-
- - ); - }; + + +
+ {square[1]} +
+
+ +
+ {square[0]} +
+
+ + ); +}; export default ChessBoard; diff --git a/client/src/components/ErrorNotification.tsx b/client/src/components/ErrorNotification.tsx index 5e3ced7..8ac9536 100644 --- a/client/src/components/ErrorNotification.tsx +++ b/client/src/components/ErrorNotification.tsx @@ -16,7 +16,7 @@ const ErrorNorification: React.FC<{ divRef.current.classList.add( "opacity-0", "transition-opacity", - "duration-300" + "duration-300", ); setTimeout(() => { diff --git a/client/src/components/History.tsx b/client/src/components/History.tsx index 67649d3..a285621 100644 --- a/client/src/components/History.tsx +++ b/client/src/components/History.tsx @@ -2,92 +2,92 @@ import { ReactNode, useEffect, useRef } from "react"; import Chess from "../../../server/src/engine"; const History: React.FC<{ - chess: Chess; - buttons?: Array<{ onClick: () => void; title: string; icon: ReactNode }>; + chess: Chess; + buttons?: Array<{ onClick: () => void; title: string; icon: ReactNode }>; }> = ({ chess, buttons }) => { - const containerRef = useRef(null); - const historyRef = useRef(null); + const containerRef = useRef(null); + const historyRef = useRef(null); - const { currentPosition, history: moveHistory } = chess.getHistory(); + const { currentPosition, history: moveHistory } = chess.getHistory(); - useEffect(() => { - // HACK: couldn't find any other way for ChessBoard and History to have the same height - setTimeout(() => { - const chessboard = document.querySelector( - "#chessboard" - ) as HTMLDivElement; - const { height } = chessboard.getBoundingClientRect(); - const containerDiv = containerRef.current as HTMLDivElement; - containerDiv.style.setProperty("max-height", `${height}px`); - }, 100); + useEffect(() => { + // HACK: couldn't find any other way for ChessBoard and History to have the same height + setTimeout(() => { + const chessboard = document.querySelector( + "#chessboard", + ) as HTMLDivElement; + const { height } = chessboard.getBoundingClientRect(); + const containerDiv = containerRef.current as HTMLDivElement; + containerDiv.style.setProperty("max-height", `${height}px`); + }, 100); - const historyDiv = historyRef.current as HTMLDivElement; - historyDiv.scrollTop = historyDiv.scrollHeight; - }, [moveHistory.length]); + const historyDiv = historyRef.current as HTMLDivElement; + historyDiv.scrollTop = historyDiv.scrollHeight; + }, [moveHistory.length]); - const history: ReactNode[] = []; + const history: ReactNode[] = []; - moveHistory.forEach(({ san }, idx) => { - const fullMove = (idx >> 1) + 1; - const current = idx == currentPosition ? "bg-gray-500/80" : ""; + moveHistory.forEach(({ san }, idx) => { + const fullMove = (idx >> 1) + 1; + const current = idx == currentPosition ? "bg-gray-500/80" : ""; - if (idx % 2 == 0) - history.push( - - {fullMove}. - - ); + if (idx % 2 == 0) + history.push( + + {fullMove}. + , + ); - history.push( - - - {san} - - - ); - }); - - if (history.length % 3) history.push(); - - return ( -
-
History
-
- {history} -
-
- {buttons?.map(({ onClick, title, icon }) => ( - - ))} -
-
+ history.push( + + + {san} + + , ); + }); + + if (history.length % 3) history.push(); + + return ( +
+
History
+
+ {history} +
+
+ {buttons?.map(({ onClick, title, icon }) => ( + + ))} +
+
+ ); }; const Button: React.FC<{ - children: React.ReactNode; - title: string; - disabled?: boolean; - onClick: () => void; + children: React.ReactNode; + title: string; + disabled?: boolean; + onClick: () => void; }> = ({ children, title, disabled, onClick }) => ( - + ); export default History; diff --git a/client/src/hooks/useCopyToClipboard.ts b/client/src/hooks/useCopyToClipboard.ts index 97e803e..3fdbc7d 100644 --- a/client/src/hooks/useCopyToClipboard.ts +++ b/client/src/hooks/useCopyToClipboard.ts @@ -1,43 +1,43 @@ import { useState, useCallback } from "react"; type State = { - error: Error | null; - text: string | null; + error: Error | null; + text: string | null; }; export default function useCopyToClipboard(): [ - State, - (value: any) => Promise + State, + (value: any) => Promise, ] { - const [state, setState] = useState({ - error: null, + const [state, setState] = useState({ + error: null, + text: null, + }); + + const copyToClipboard = useCallback(async (value: string) => { + if (!navigator?.clipboard) { + return setState({ + error: new Error("Clipboard not supported"), 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 handleSuccess = () => { - setState({ - error: null, - text: value, - }); - }; + const handleFailure = (e: any) => { + setState({ + error: e, + text: null, + }); + }; - const handleFailure = (e: any) => { - setState({ - error: e, - text: null, - }); - }; + navigator.clipboard.writeText(value).then(handleSuccess, handleFailure); + }, []); - navigator.clipboard.writeText(value).then(handleSuccess, handleFailure); - }, []); - - return [state, copyToClipboard]; + return [state, copyToClipboard]; } diff --git a/client/src/main.tsx b/client/src/main.tsx index bd387a7..b859f33 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -45,5 +45,5 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - + , ); diff --git a/client/src/routes/LocalGame.tsx b/client/src/routes/LocalGame.tsx index 13a692c..56a1740 100644 --- a/client/src/routes/LocalGame.tsx +++ b/client/src/routes/LocalGame.tsx @@ -3,71 +3,71 @@ import { useNavigate } from "react-router-dom"; import Chess, { Move } from "../../../server/src/engine"; import ChessUI from "../components/ChessUI"; import { - ArrowLeft, - CaretLeft, - CaretRight, - Plus, - Repeat, + ArrowLeft, + CaretLeft, + CaretRight, + Plus, + Repeat, } from "../components/icons"; import InferProps from "../utils/InferProps"; const LocalGame = () => { - const [chess, setChess] = useState(Chess.load()); - const [blackPerspective, setBlackPerspective] = useState(false); - const [, aux] = useState(false); - const navigate = useNavigate(); + const [chess, setChess] = useState(Chess.load()); + const [blackPerspective, setBlackPerspective] = useState(false); + const [, aux] = useState(false); + const navigate = useNavigate(); - const rerender = () => aux((prev) => !prev); + const rerender = () => aux((prev) => !prev); - const makeMove = (move: Move) => { - if (chess.didUndo()) chess.undoMoves(); + const makeMove = (move: Move) => { + if (chess.didUndo()) chess.undoMoves(); - chess.makeMove(move); + chess.makeMove(move); + rerender(); + }; + + const buttons: Pick, "buttons">["buttons"] = [ + { + onClick: () => navigate("/"), + title: "to main page", + icon: , + }, + { + onClick: () => setChess(Chess.load()), + title: "new game", + icon: , + }, + { + onClick: () => setBlackPerspective((prev) => !prev), + title: "switch sides", + icon: , + }, + { + onClick: () => { + chess.undo(); rerender(); - }; + }, + title: "undo", + icon: , + }, + { + onClick: () => { + chess.redo(); + rerender(); + }, + title: "redo", + icon: , + }, + ]; - const buttons: Pick, "buttons">["buttons"] = [ - { - onClick: () => navigate("/"), - title: "to main page", - icon: , - }, - { - onClick: () => setChess(Chess.load()), - title: "new game", - icon: , - }, - { - onClick: () => setBlackPerspective((prev) => !prev), - title: "switch sides", - icon: , - }, - { - onClick: () => { - chess.undo(); - rerender(); - }, - title: "undo", - icon: , - }, - { - onClick: () => { - chess.redo(); - rerender(); - }, - title: "redo", - icon: , - }, - ]; - - return ( - - ); + return ( + + ); }; export default LocalGame; diff --git a/client/src/utils/InferProps.ts b/client/src/utils/InferProps.ts index 9e20629..f943507 100644 --- a/client/src/utils/InferProps.ts +++ b/client/src/utils/InferProps.ts @@ -4,14 +4,14 @@ type ArgumentTypes> = F extends React.FC type Head>> = T extends [ infer TT extends React.FC, - ...any + ...any, ] ? TT : never; type Tail>> = T extends [ any, - ...infer TT extends Array> + ...infer TT extends Array>, ] ? TT : never; diff --git a/client/src/utils/socket.ts b/client/src/utils/socket.ts index 454d942..09366d0 100644 --- a/client/src/utils/socket.ts +++ b/client/src/utils/socket.ts @@ -2,8 +2,8 @@ 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"; + process.env.NODE_ENV === "production" ? undefined : "http://localhost:5000"; export const socket = io(URL as string, { - autoConnect: false, + autoConnect: false, }); diff --git a/server/src/__test__/gameOver.test.ts b/server/src/__test__/gameOver.test.ts index 91a41f9..b31a725 100644 --- a/server/src/__test__/gameOver.test.ts +++ b/server/src/__test__/gameOver.test.ts @@ -16,15 +16,15 @@ describe("checkmate", () => { test("Fool's Mate", () => { expect( Chess.load( - "rnb1kbnr/pppp1ppp/8/4p3/6Pq/5P2/PPPPP2P/RNBQKBNR w KQkq - 1 3" - ).isCheckMate() + "rnb1kbnr/pppp1ppp/8/4p3/6Pq/5P2/PPPPP2P/RNBQKBNR w KQkq - 1 3", + ).isCheckMate(), ).toBe(true); }); test("Scholar's Mate", () => { expect( Chess.load( - "rnb1kbnr/pppp1ppp/8/4p3/6Pq/5P2/PPPPP2P/RNBQKBNR w KQkq - 1 3" - ).isCheckMate() + "rnb1kbnr/pppp1ppp/8/4p3/6Pq/5P2/PPPPP2P/RNBQKBNR w KQkq - 1 3", + ).isCheckMate(), ).toBe(true); }); }); @@ -35,19 +35,19 @@ describe("stalemate", () => { }); test("Rosen Trap", () => { expect(Chess.load("8/8/6k1/8/8/8/5q2/7K w - - 0 1").isStalemate()).toBe( - true + true, ); }); test("Troitsky vs. Vogt, 1896", () => { expect( Chess.load( - "3k4/1pp2pp1/1b4r1/pP2p3/P3P3/6Nb/5P1P/3qB1KR w - - 0 1" - ).isStalemate() + "3k4/1pp2pp1/1b4r1/pP2p3/P3P3/6Nb/5P1P/3qB1KR w - - 0 1", + ).isStalemate(), ).toBe(true); }); test("Lukany vs. Smulyan, 1938", () => { expect( - Chess.load("8/8/8/p1K5/k1P1P3/PpP5/1P6/8 b - - 0 1").isStalemate() + Chess.load("8/8/8/p1K5/k1P1P3/PpP5/1P6/8 b - - 0 1").isStalemate(), ).toBe(true); }); }); @@ -82,7 +82,7 @@ describe("threefold repetition", () => { { to: "e8", from: "e7" }, { to: "e2", from: "e1" }, { to: "e7", from: "e8" }, - ] + ], ); expect(chess.isThreefoldRepetition()).toBe(true); }); @@ -199,31 +199,31 @@ describe("50 moves", () => { describe("insufficient material", () => { test("2 kings", () => { expect( - Chess.load("8/k7/8/6K1/8/8/8/8 w - - 0 1").isInsufficientMaterial() + Chess.load("8/k7/8/6K1/8/8/8/8 w - - 0 1").isInsufficientMaterial(), ).toBe(true); }); test("king and bishop", () => { expect( - Chess.load("5b2/k7/8/6K1/8/2B5/8/8 w - - 0 1").isInsufficientMaterial() + Chess.load("5b2/k7/8/6K1/8/2B5/8/8 w - - 0 1").isInsufficientMaterial(), ).toBe(true); }); test("king and knight", () => { expect( - Chess.load("8/k7/8/3n2K1/8/5N2/8/8 w - - 0 1").isInsufficientMaterial() + Chess.load("8/k7/8/3n2K1/8/5N2/8/8 w - - 0 1").isInsufficientMaterial(), ).toBe(true); }); test("king and knight vs king and bishop", () => { expect( - Chess.load("8/k3n3/8/6K1/8/2B5/8/8 w - - 0 1").isInsufficientMaterial() + Chess.load("8/k3n3/8/6K1/8/2B5/8/8 w - - 0 1").isInsufficientMaterial(), ).toBe(true); }); test("king and 2 knights", () => { expect( - Chess.load("8/k7/4N3/6K1/8/5N2/8/8 w - - 0 1").isInsufficientMaterial() + Chess.load("8/k7/4N3/6K1/8/5N2/8/8 w - - 0 1").isInsufficientMaterial(), ).toBe(true); }); @@ -233,39 +233,39 @@ describe("insufficient material", () => { test("king and 2 bishops vs king", () => { expect( - Chess.load("8/k7/8/4B1K1/4B3/8/8/8 w - - 0 1").isInsufficientMaterial() + Chess.load("8/k7/8/4B1K1/4B3/8/8/8 w - - 0 1").isInsufficientMaterial(), ).toBe(false); }); test("king and 2 knights vs king and bishop", () => { expect( - Chess.load("8/k7/3n4/6K1/2n1B3/8/8/8 w - - 0 1").isInsufficientMaterial() + Chess.load("8/k7/3n4/6K1/2n1B3/8/8/8 w - - 0 1").isInsufficientMaterial(), ).toBe(false); }); test("king and 2 knights vs king and knight", () => { expect( - Chess.load("6N1/k7/3n4/6K1/2n5/8/8/8 w - - 0 1").isInsufficientMaterial() + Chess.load("6N1/k7/3n4/6K1/2n5/8/8/8 w - - 0 1").isInsufficientMaterial(), ).toBe(false); }); test("king and rook vs king", () => { expect( - Chess.load("5r2/k7/8/6K1/8/8/8/8 w - - 0 1").isInsufficientMaterial() + Chess.load("5r2/k7/8/6K1/8/8/8/8 w - - 0 1").isInsufficientMaterial(), ).toBe(false); }); test("king and queen vs king", () => { expect( - Chess.load("5Q2/k7/8/6K1/8/8/8/8 w - - 0 1").isInsufficientMaterial() + Chess.load("5Q2/k7/8/6K1/8/8/8/8 w - - 0 1").isInsufficientMaterial(), ).toBe(false); }); test("only pawns remaining", () => { expect( Chess.load( - "1k6/3p4/1p6/6K1/5P2/4P3/8/8 w - - 0 1" - ).isInsufficientMaterial() + "1k6/3p4/1p6/6K1/5P2/4P3/8/8 w - - 0 1", + ).isInsufficientMaterial(), ).toBe(false); }); }); diff --git a/server/src/__test__/getMoves.test.ts b/server/src/__test__/getMoves.test.ts index 1429104..0f65781 100644 --- a/server/src/__test__/getMoves.test.ts +++ b/server/src/__test__/getMoves.test.ts @@ -44,17 +44,17 @@ function runTest(fen: string, expectedMoves: ExpectedMoves, color?: Color) { expect(moves).toHaveLength(expectedMoves.length); moves.forEach((move) => expect(expectedMoves).toContainEqual(move)); expectedMoves.forEach((expectedMove) => - expect(moves).toContainEqual(expectedMove) + expect(moves).toContainEqual(expectedMove), ); - }) - ) + }), + ), ); } function runTests( fen: string, expectedMovesW: ExpectedMoves, - expectedMovesB: ExpectedMoves + expectedMovesB: ExpectedMoves, ) { runTest(fen.replace("%", "w"), expectedMovesW, COLOR.WHITE); runTest(fen.replace("%", "b"), expectedMovesB, COLOR.BLACK); @@ -205,7 +205,7 @@ describe("pawn moves", () => { runTests( "r6k/1Pp1p2p/2P2p2/4p3/3P4/2P5/P5p1/K6R % - - 0 1", expectedMovesW, - expectedMovesB + expectedMovesB, ); }); @@ -299,7 +299,7 @@ describe("knight moves", () => { runTests( "n7/Kn6/8/3kN3/8/2n5/N5N1/7N % - - 0 1", expectedMovesW, - expectedMovesB + expectedMovesB, ); }); @@ -415,7 +415,7 @@ describe("bishop moves", () => { runTests( "K6b/8/3b4/2b3b1/8/8/B1BB4/7k % - - 0 1", expectedMovesW, - expectedMovesB + expectedMovesB, ); }); @@ -580,7 +580,7 @@ describe("en passant", () => { runTest( "rnbqkbnr/pp1p1ppp/8/2pPp3/8/8/PPP1PPPP/RNBQKBNR w KQkq e6 0 3", - expectedMoves + expectedMoves, ); }); @@ -600,6 +600,6 @@ describe("castling", () => { runTest( "rn1qkbnr/p4ppp/B1p5/1p1pp3/3PPBb1/2NQ3N/PPP2PPP/R3K2R w KQkq - 0 8", - expectedMoves + expectedMoves, ); }); diff --git a/server/src/__test__/loadFEN.test.ts b/server/src/__test__/loadFEN.test.ts index 9056199..511a2b3 100644 --- a/server/src/__test__/loadFEN.test.ts +++ b/server/src/__test__/loadFEN.test.ts @@ -89,7 +89,7 @@ describe("valid FEN strings", () => { const expected = builder.build(); const chess = Chess.load( - "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1" + "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1", ); for (let i = 0; i < 64; i++) @@ -137,7 +137,7 @@ describe("valid FEN strings", () => { const expected = builder.build(); const chess = Chess.load( - "rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 2" + "rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 2", ); for (let i = 0; i < 64; i++) @@ -185,7 +185,7 @@ describe("valid FEN strings", () => { const expected = builder.build(); const chess = Chess.load( - "rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPPKPPP/RNBQ1BNR b kq - 1 2" + "rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPPKPPP/RNBQ1BNR b kq - 1 2", ); for (let i = 0; i < 64; i++) @@ -221,7 +221,7 @@ describe("valid FEN strings", () => { const expected = builder.build(); const chess = Chess.load( - "r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - - 0 19" + "r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - - 0 19", ); for (let i = 0; i < 64; i++) @@ -277,25 +277,25 @@ describe("valid FEN strings", () => { describe("invalid FEN strings", () => { test("string doesn't contain 6 fields", () => { expect(() => Chess.load("8/8/2k2Q2/8/5P2/8/1p6/6K1 b - 1 48")).toThrowError( - /^Invalid FEN - string must contain 6 space delimited fields$/ + /^Invalid FEN - string must contain 6 space delimited fields$/, ); }); test("missing white king", () => { expect(() => Chess.load("8/8/2k2Q2/8/5P2/8/1p6/8 b - - 1 48")).toThrowError( - /^Invalid FEN - board position is missing white king$/ + /^Invalid FEN - board position is missing white king$/, ); }); test("invalid turn", () => { expect(() => - Chess.load("1k6/1pp5/p7/5B1p/PP6/6K1/4p2r/4R3 a - - 3 43") + Chess.load("1k6/1pp5/p7/5B1p/PP6/6K1/4p2r/4R3 a - - 3 43"), ).toThrowError(/^Invalid FEN - invalid side to move$/); }); test("invalid castling rights", () => { expect(() => - Chess.load("1k6/1pp5/p7/5B1p/PP6/6K1/4p2r/4R3 b abc - 3 43") + Chess.load("1k6/1pp5/p7/5B1p/PP6/6K1/4p2r/4R3 b abc - 3 43"), ).toThrowError(/^Invalid FEN - string contains invalid castling rights$/); }); }); diff --git a/server/src/__test__/validateFEN.test.ts b/server/src/__test__/validateFEN.test.ts index 8e13ffb..d2cfc84 100644 --- a/server/src/__test__/validateFEN.test.ts +++ b/server/src/__test__/validateFEN.test.ts @@ -8,33 +8,35 @@ describe("valid FEN strings", () => { test("1. e4", () => { expect( - validateFEN("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1") + validateFEN( + "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1", + ), ).toBeUndefined(); }); test("1. e4 2. e5", () => { expect( validateFEN( - "rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 2" - ) + "rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 2", + ), ).toBeUndefined(); }); test("1. e4 2. e5 3. ke2", () => { expect( - validateFEN("rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPPKPPP/RNBQ1BNR b kq - 1 2") + validateFEN("rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPPKPPP/RNBQ1BNR b kq - 1 2"), ).toBeUndefined(); }); test("random position 1", () => { expect( - validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - - 0 19") + validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - - 0 19"), ).toBeUndefined(); }); test("random position 2", () => { expect( - validateFEN("1k6/1pp5/p7/5B1p/PP6/6K1/4p2r/4R3 b - - 3 43") + validateFEN("1k6/1pp5/p7/5B1p/PP6/6K1/4p2r/4R3 b - - 3 43"), ).toBeUndefined(); }); @@ -46,70 +48,70 @@ describe("valid FEN strings", () => { describe("invalid FEN strings", () => { test("string doesn't contain 6 fields", () => { expect(() => - validateFEN("8/8/2k2Q2/8/5P2/8/1p6/6K1 b - 1 48") + validateFEN("8/8/2k2Q2/8/5P2/8/1p6/6K1 b - 1 48"), ).toThrowError( - /^Invalid FEN - string must contain 6 space delimited fields$/ + /^Invalid FEN - string must contain 6 space delimited fields$/, ); }); describe("invalid board position", () => { test("doesn't contain 8 fields/rows", () => { expect(() => - validateFEN("8/8/2k2Q2/8/5P2/8/1p6 b - - 1 48") + validateFEN("8/8/2k2Q2/8/5P2/8/1p6 b - - 1 48"), ).toThrowError( - /^Invalid FEN - board position must contain 8 rows delimited by '\/'$/ + /^Invalid FEN - board position must contain 8 rows delimited by '\/'$/, ); }); test("missing white king", () => { expect(() => - validateFEN("8/8/2k2Q2/8/5P2/8/1p6/8 b - - 1 48") + validateFEN("8/8/2k2Q2/8/5P2/8/1p6/8 b - - 1 48"), ).toThrowError(/^Invalid FEN - board position is missing white king$/); }); test("missing black king", () => { expect(() => - validateFEN("8/8/2K2Q2/8/5P2/8/1p6/8 b - - 1 48") + validateFEN("8/8/2K2Q2/8/5P2/8/1p6/8 b - - 1 48"), ).toThrowError(/^Invalid FEN - board position is missing black king$/); }); test("too many white kings", () => { expect(() => - validateFEN("8/7K/2k2Q2/8/5P2/8/1p6/6K1 b - - 1 48") + validateFEN("8/7K/2k2Q2/8/5P2/8/1p6/6K1 b - - 1 48"), ).toThrowError( - /^Invalid FEN - board position contains too many white kings$/ + /^Invalid FEN - board position contains too many white kings$/, ); }); test("too many black kings", () => { expect(() => - validateFEN("8/7k/2k2Q2/8/5P2/8/1p6/6K1 b - - 1 48") + validateFEN("8/7k/2k2Q2/8/5P2/8/1p6/6K1 b - - 1 48"), ).toThrowError( - /^Invalid FEN - board position contains too many black kings$/ + /^Invalid FEN - board position contains too many black kings$/, ); }); test("consecutive digits", () => { expect(() => - validateFEN("8/62/2k2Q2/8/5P2/8/1p6/6K1 b - - 1 48") + validateFEN("8/62/2k2Q2/8/5P2/8/1p6/6K1 b - - 1 48"), ).toThrowError( - /^Invalid FEN - board position contains consecutive digits$/ + /^Invalid FEN - board position contains consecutive digits$/, ); }); test("invalid piece", () => { expect(() => - validateFEN("1k6/1Gp5/p7/5B1p/PP6/6K1/4p2r/4R3 b - - 3 43") + validateFEN("1k6/1Gp5/p7/5B1p/PP6/6K1/4p2r/4R3 b - - 3 43"), ).toThrowError( - /^Invalid FEN - board position contains an invalid piece symbol: G$/ + /^Invalid FEN - board position contains an invalid piece symbol: G$/, ); }); test("too many squares on row", () => { expect(() => - validateFEN("1k6/1pp5/1p7/5B1p/PP6/6K1/4p2r/4R3 b - - 3 43") + validateFEN("1k6/1pp5/1p7/5B1p/PP6/6K1/4p2r/4R3 b - - 3 43"), ).toThrowError( - /^Invalid FEN - board position contains a row that does not have 8 squares$/ + /^Invalid FEN - board position contains a row that does not have 8 squares$/, ); }); }); @@ -117,19 +119,19 @@ describe("invalid FEN strings", () => { describe("invalid turn", () => { test("capital W", () => { expect(() => - validateFEN("1k6/1pp5/p7/5B1p/PP6/6K1/4p2r/4R3 W - - 3 43") + validateFEN("1k6/1pp5/p7/5B1p/PP6/6K1/4p2r/4R3 W - - 3 43"), ).toThrowError(/^Invalid FEN - invalid side to move$/); }); test("capital B", () => { expect(() => - validateFEN("1k6/1pp5/p7/5B1p/PP6/6K1/4p2r/4R3 B - - 3 43") + validateFEN("1k6/1pp5/p7/5B1p/PP6/6K1/4p2r/4R3 B - - 3 43"), ).toThrowError(/^Invalid FEN - invalid side to move$/); }); test("invalid letter", () => { expect(() => - validateFEN("1k6/1pp5/p7/5B1p/PP6/6K1/4p2r/4R3 c - - 3 43") + validateFEN("1k6/1pp5/p7/5B1p/PP6/6K1/4p2r/4R3 c - - 3 43"), ).toThrowError(/^Invalid FEN - invalid side to move$/); }); }); @@ -137,7 +139,7 @@ describe("invalid FEN strings", () => { describe("invalid castling rights", () => { test("invalid characters", () => { expect(() => - validateFEN("1k6/1pp5/p7/5B1p/PP6/6K1/4p2r/4R3 b abc - 3 43") + validateFEN("1k6/1pp5/p7/5B1p/PP6/6K1/4p2r/4R3 b abc - 3 43"), ).toThrowError(/^Invalid FEN - string contains invalid castling rights$/); }); }); @@ -145,19 +147,19 @@ describe("invalid FEN strings", () => { describe("invalid en-passant", () => { test("random position 1", () => { expect(() => - validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - e1 0 19") + validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - e1 0 19"), ).toThrowError(/^Invalid FEN - invalid en-passant square$/); }); test("random position 1", () => { expect(() => - validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 w - e3 0 19") + validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 w - e3 0 19"), ).toThrowError(/^Invalid FEN - invalid en-passant square$/); }); test("random position 1", () => { expect(() => - validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - e6 0 19") + validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - e6 0 19"), ).toThrowError(/^Invalid FEN - invalid en-passant square$/); }); }); @@ -165,17 +167,17 @@ describe("invalid FEN strings", () => { describe("invalid half moves", () => { test("negative number", () => { expect(() => - validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - - -2 19") + validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - - -2 19"), ).toThrowError( - /^Invalid FEN - move number must be a non-negative integer$/ + /^Invalid FEN - move number must be a non-negative integer$/, ); }); test("not a number", () => { expect(() => - validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - - abc 19") + validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - - abc 19"), ).toThrowError( - /^Invalid FEN - move number must be a non-negative integer$/ + /^Invalid FEN - move number must be a non-negative integer$/, ); }); }); @@ -183,25 +185,25 @@ describe("invalid FEN strings", () => { describe("invalid full moves", () => { test("negative number", () => { expect(() => - validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - - 0 -3") + validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - - 0 -3"), ).toThrowError( - /^Invalid FEN - number of full moves must be a positive integer$/ + /^Invalid FEN - number of full moves must be a positive integer$/, ); }); test("zero", () => { expect(() => - validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - - 0 0") + validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - - 0 0"), ).toThrowError( - /^Invalid FEN - number of full moves must be a positive integer$/ + /^Invalid FEN - number of full moves must be a positive integer$/, ); }); test("not a number", () => { expect(() => - validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - - 0 abc") + validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - - 0 abc"), ).toThrowError( - /^Invalid FEN - number of full moves must be a positive integer$/ + /^Invalid FEN - number of full moves must be a positive integer$/, ); }); }); diff --git a/server/src/engine.ts b/server/src/engine.ts index 1a052f8..fb2444c 100644 --- a/server/src/engine.ts +++ b/server/src/engine.ts @@ -145,7 +145,7 @@ export function validateFEN(fen: string): void { if (fields.length != 6) throw new Error( - "Invalid FEN - string must contain 6 space delimited fields" + "Invalid FEN - string must contain 6 space delimited fields", ); const validatePosition = (position: string) => { @@ -153,7 +153,7 @@ export function validateFEN(fen: string): void { if (rows.length != 8) throw new Error( - "Invalid FEN - board position must contain 8 rows delimited by '/'" + "Invalid FEN - board position must contain 8 rows delimited by '/'", ); const kings = [ @@ -166,12 +166,12 @@ export function validateFEN(fen: string): void { if (matches.length == 0) throw new Error( - `Invalid FEN - board position is missing ${king.color} king` + `Invalid FEN - board position is missing ${king.color} king`, ); if (matches.length > 1) throw new Error( - `Invalid FEN - board position contains too many ${king.color} kings` + `Invalid FEN - board position contains too many ${king.color} kings`, ); } @@ -183,7 +183,7 @@ export function validateFEN(fen: string): void { if (isDigit(symbol)) { if (previousWasNumber) throw new Error( - "Invalid FEN - board position contains consecutive digits" + "Invalid FEN - board position contains consecutive digits", ); numSquares += parseInt(symbol); @@ -193,12 +193,12 @@ export function validateFEN(fen: string): void { if ( !(Object.values(PIECE) as Array).includes( - symbol.toLowerCase() + symbol.toLowerCase(), ) ) throw new Error( "Invalid FEN - board position contains an invalid piece symbol: " + - symbol + symbol, ); numSquares++; @@ -207,7 +207,7 @@ export function validateFEN(fen: string): void { if (numSquares != 8) throw new Error( - "Invalid FEN - board position contains a row that does not have 8 squares" + "Invalid FEN - board position contains a row that does not have 8 squares", ); }); }; @@ -233,14 +233,14 @@ export function validateFEN(fen: string): void { const validateHalfMoves = (halfMoves: string) => { if (/^\d+$/.test(halfMoves) == false) throw new Error( - "Invalid FEN - move number must be a non-negative integer" + "Invalid FEN - move number must be a non-negative integer", ); }; const validateFullMoves = (fullMoves: string) => { if (/^[1-9]\d*$/.test(fullMoves) == false) throw new Error( - "Invalid FEN - number of full moves must be a positive integer" + "Invalid FEN - number of full moves must be a positive integer", ); }; @@ -326,7 +326,7 @@ function generatePawnMoves( position: number, color: Color, board: Readonly, - enPassantSquare: Square + enPassantSquare: Square, ): Array { if (board[position] == null) return []; @@ -343,7 +343,7 @@ function generatePawnMoves( to: toAlgebraic, promotion: piece, flags: MOVE_FLAGS.PROMOTION | (flag ?? 0), - }) + }), ); }; @@ -404,7 +404,7 @@ function generatePieceMoves( position: number, piece: Piece, board: Readonly, - enPassantSquare: Square + enPassantSquare: Square, ): Array { if (piece.type == PIECE.PAWN) return generatePawnMoves(position, piece.color, board, enPassantSquare); @@ -524,7 +524,7 @@ export default class Chess { fullMoves: number, kings: Record, enableProcessMoves: boolean, - enableHistory: boolean + enableHistory: boolean, ) { this._board = board; this._turn = turn; @@ -549,7 +549,8 @@ export default class Chess { if (this._turn == piece.color) this._moves = this._moves.concat(moves); moves.forEach( - ({ to }) => (this._attacks[squareIndex(to)] |= COLOR_MASKS[piece.color]) + ({ to }) => + (this._attacks[squareIndex(to)] |= COLOR_MASKS[piece.color]), ); } @@ -629,7 +630,7 @@ export default class Chess { { enableProcessMoves, enableHistory, - }: { enableProcessMoves: boolean; enableHistory: boolean } + }: { enableProcessMoves: boolean; enableHistory: boolean }, ) { validateFEN(fen); @@ -777,7 +778,7 @@ export default class Chess { switch (move.flags) { case MOVE_FLAGS.PAWN_JUMP: this._enPassant = algebraic( - squareIndex(move.to) - PAWN_MOVE_INFO[myColor].offset + squareIndex(move.to) - PAWN_MOVE_INFO[myColor].offset, ); keepEpSquare = true; break; @@ -1054,7 +1055,7 @@ export default class Chess { chess._undoCount = 0; chess._history = this._history.slice( 0, - this._history.length - this._undoCount + this._history.length - this._undoCount, ); return chess; @@ -1154,7 +1155,7 @@ export default class Chess { this._fullMoves, this._kings, this._enableProcessMoves, - this._enableHistory + this._enableHistory, ); } }; diff --git a/server/src/server.ts b/server/src/server.ts index 164822b..b543111 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -26,7 +26,7 @@ const io = new Server( origin: "http://localhost:3000", }, } - : {} + : {}, ); type Room = { @@ -105,7 +105,7 @@ if (process.env.NODE_ENV === "development") w: room.w, b: room.b, game: room.game.getFEN(), - }) + }), ); return res.json(Object.fromEntries(m)); }); @@ -140,7 +140,7 @@ if (process.env.NODE_ENV === "production") { app.use(express.static(path.join(__dirname, "client", "dist"))); app.get("*", (_req, res) => - res.sendFile(path.resolve(__dirname, "client", "dist", "index.html")) + res.sendFile(path.resolve(__dirname, "client", "dist", "index.html")), ); } else app.get("/", (_req, res) => res.send("Hello, world!")); @@ -161,5 +161,5 @@ app.use((err: Error, _req: Request, res: Response) => { }); server.listen(PORT, () => - console.log("Server listening on http://localhost:" + PORT) + console.log("Server listening on http://localhost:" + PORT), );