fix: coordinate letters, formatting
This commit is contained in:
@@ -3,4 +3,4 @@ export default {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ import Modal, { ModalButton } from "./Modal";
|
||||
import { useState } from "react";
|
||||
|
||||
const ChessUI: React.FC<InferProps<[typeof ChessBoard, typeof History]>> = (
|
||||
props
|
||||
props,
|
||||
) => {
|
||||
const [showModal, setShowModal] = useState(true);
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -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 (
|
||||
<div
|
||||
className={[
|
||||
"relative aspect-square font-bold text-xl group [&>*]: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 && (
|
||||
<img
|
||||
src={PIECES[piece.color][piece.type]}
|
||||
className="max-w-full max-h-full aspect-square"
|
||||
/>
|
||||
)}
|
||||
<Show when={endGameIcon != undefined}>
|
||||
<div className="absolute top-0 left-full -translate-x-1/2 -translate-y-1/2 bg-white z-10 rounded-full p-1 shadow-lg shadow-black aspect-square flex justify-center items-center">
|
||||
{endGameIcon}
|
||||
</div>
|
||||
if (isFirstColumn || isLastRow) console.log(square);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
"relative isolate aspect-square font-bold text-xl group [&>*]: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 && (
|
||||
<img
|
||||
src={PIECES[piece.color][piece.type]}
|
||||
className="max-w-full max-h-full aspect-square"
|
||||
/>
|
||||
)}
|
||||
<Show when={endGameIcon != undefined}>
|
||||
<div className="absolute top-0 left-full -translate-x-1/2 -translate-y-1/2 bg-white z-10 rounded-full p-1 shadow-lg shadow-black aspect-square flex justify-center items-center">
|
||||
{endGameIcon}
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={isAttacked}>
|
||||
<Show
|
||||
when={piece == null}
|
||||
fallback={
|
||||
<div className="absolute z-10 -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2 w-full aspect-square border-8 border-gray-900/40 rounded-full"></div>
|
||||
}
|
||||
>
|
||||
<div className="absolute z-10 -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2 w-[35%] aspect-square bg-gray-900/40 rounded-full group-hover:w-[45%]"></div>
|
||||
</Show>
|
||||
<Show when={isAttacked}>
|
||||
<Show
|
||||
when={piece == null}
|
||||
fallback={
|
||||
<div className="absolute -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2 w-full aspect-square border-8 border-gray-900/40 rounded-full"></div>
|
||||
}
|
||||
>
|
||||
<div className="absolute -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2 w-[35%] aspect-square bg-gray-900/40 rounded-full group-hover:w-[45%]"></div>
|
||||
</Show>
|
||||
</Show>
|
||||
<Show when={isFirstColumn}>
|
||||
<div className={`absolute top-1 left-1 -z-10 ${textColor}`}>
|
||||
{square[1]}
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={isLastRow}>
|
||||
<div className={`absolute bottom-1 right-1 -z-10 ${textColor}`}>
|
||||
{square[0]}
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</Show>
|
||||
<Show when={isFirstColumn}>
|
||||
<div className={`absolute top-1 left-1 -z-10 ${textColor}`}>
|
||||
{square[1]}
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={isLastRow}>
|
||||
<div className={`absolute bottom-1 right-1 -z-10 ${textColor}`}>
|
||||
{square[0]}
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChessBoard;
|
||||
|
||||
@@ -16,7 +16,7 @@ const ErrorNorification: React.FC<{
|
||||
divRef.current.classList.add(
|
||||
"opacity-0",
|
||||
"transition-opacity",
|
||||
"duration-300"
|
||||
"duration-300",
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -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<HTMLDivElement>(null);
|
||||
const historyRef = useRef<HTMLDivElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const historyRef = useRef<HTMLDivElement>(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(
|
||||
<span key={idx} className="px-2 py-1">
|
||||
{fullMove}.
|
||||
</span>
|
||||
);
|
||||
if (idx % 2 == 0)
|
||||
history.push(
|
||||
<span key={idx} className="px-2 py-1">
|
||||
{fullMove}.
|
||||
</span>,
|
||||
);
|
||||
|
||||
history.push(
|
||||
<span key={idx + san} className="py-1">
|
||||
<span className={`inline-block h-full w-2/3 rounded-md ${current}`}>
|
||||
{san}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
if (history.length % 3) history.push(<span key="__history-last__"></span>);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="bg-zinc-800 rounded-lg text-white hidden md:grid grid-rows-[auto,1fr,auto]"
|
||||
>
|
||||
<div className="text-xl text-center my-1 mx-4 border-b">History</div>
|
||||
<div
|
||||
ref={historyRef}
|
||||
className="p-4 min-w-[13rem] h-fit max-h-full overflow-y-scroll overflow-x-hidden grid grid-cols-[auto,1fr,1fr] gap-y-2 text-center [&>:nth-child(3n)]:rounded-r-md [&>:nth-child(3n-2)]:rounded-l-md [&>:not(:nth-child(3n-2))]:font-bold [&>:nth-child(6n)]:bg-zinc-600 [&>:nth-child(6n-1)]:bg-zinc-600 [&>:nth-child(6n-2)]:bg-zinc-600"
|
||||
>
|
||||
{history}
|
||||
</div>
|
||||
<div className="bg-black grid auto-cols-fr grid-flow-col gap-5 p-4 rounded-b-lg empty:hidden">
|
||||
{buttons?.map(({ onClick, title, icon }) => (
|
||||
<Button key={title} title={title} onClick={onClick}>
|
||||
{icon}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
history.push(
|
||||
<span key={idx + san} className="py-1">
|
||||
<span className={`inline-block h-full w-2/3 rounded-md ${current}`}>
|
||||
{san}
|
||||
</span>
|
||||
</span>,
|
||||
);
|
||||
});
|
||||
|
||||
if (history.length % 3) history.push(<span key="__history-last__"></span>);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="bg-zinc-800 rounded-lg text-white hidden md:grid grid-rows-[auto,1fr,auto]"
|
||||
>
|
||||
<div className="text-xl text-center my-1 mx-4 border-b">History</div>
|
||||
<div
|
||||
ref={historyRef}
|
||||
className="p-4 min-w-[13rem] h-fit max-h-full overflow-y-scroll overflow-x-hidden grid grid-cols-[auto,1fr,1fr] gap-y-2 text-center [&>:nth-child(3n)]:rounded-r-md [&>:nth-child(3n-2)]:rounded-l-md [&>:not(:nth-child(3n-2))]:font-bold [&>:nth-child(6n)]:bg-zinc-600 [&>:nth-child(6n-1)]:bg-zinc-600 [&>:nth-child(6n-2)]:bg-zinc-600"
|
||||
>
|
||||
{history}
|
||||
</div>
|
||||
<div className="bg-black grid auto-cols-fr grid-flow-col gap-5 p-4 rounded-b-lg empty:hidden">
|
||||
{buttons?.map(({ onClick, title, icon }) => (
|
||||
<Button key={title} title={title} onClick={onClick}>
|
||||
{icon}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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 }) => (
|
||||
<button
|
||||
className="relative min-w-[1rem] mt-auto bg-zinc-800 flex justify-center items-center p-2 rounded-md hover:bg-zinc-900 transition-colors group capitalize"
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
<span className="absolute -top-full left-1/2 -translate-x-1/2 bg-black rounded-md py-0.5 px-2 whitespace-nowrap opacity-0 pointer-events-none group-hover:opacity-100 transition-opacity delay-100">
|
||||
{title}
|
||||
</span>
|
||||
{children}
|
||||
</button>
|
||||
<button
|
||||
className="relative min-w-[1rem] mt-auto bg-zinc-800 flex justify-center items-center p-2 rounded-md hover:bg-zinc-900 transition-colors group capitalize"
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
<span className="absolute -top-full left-1/2 -translate-x-1/2 bg-black rounded-md py-0.5 px-2 whitespace-nowrap opacity-0 pointer-events-none group-hover:opacity-100 transition-opacity delay-100">
|
||||
{title}
|
||||
</span>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
||||
export default History;
|
||||
|
||||
@@ -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<void>
|
||||
State,
|
||||
(value: any) => Promise<void>,
|
||||
] {
|
||||
const [state, setState] = useState<State>({
|
||||
error: null,
|
||||
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 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];
|
||||
}
|
||||
|
||||
+1
-1
@@ -45,5 +45,5 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<RouterProvider router={router} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</>,
|
||||
);
|
||||
|
||||
@@ -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<InferProps<[typeof ChessUI]>, "buttons">["buttons"] = [
|
||||
{
|
||||
onClick: () => navigate("/"),
|
||||
title: "to main page",
|
||||
icon: <ArrowLeft />,
|
||||
},
|
||||
{
|
||||
onClick: () => setChess(Chess.load()),
|
||||
title: "new game",
|
||||
icon: <Plus />,
|
||||
},
|
||||
{
|
||||
onClick: () => setBlackPerspective((prev) => !prev),
|
||||
title: "switch sides",
|
||||
icon: <Repeat />,
|
||||
},
|
||||
{
|
||||
onClick: () => {
|
||||
chess.undo();
|
||||
rerender();
|
||||
};
|
||||
},
|
||||
title: "undo",
|
||||
icon: <CaretLeft />,
|
||||
},
|
||||
{
|
||||
onClick: () => {
|
||||
chess.redo();
|
||||
rerender();
|
||||
},
|
||||
title: "redo",
|
||||
icon: <CaretRight />,
|
||||
},
|
||||
];
|
||||
|
||||
const buttons: Pick<InferProps<[typeof ChessUI]>, "buttons">["buttons"] = [
|
||||
{
|
||||
onClick: () => navigate("/"),
|
||||
title: "to main page",
|
||||
icon: <ArrowLeft />,
|
||||
},
|
||||
{
|
||||
onClick: () => setChess(Chess.load()),
|
||||
title: "new game",
|
||||
icon: <Plus />,
|
||||
},
|
||||
{
|
||||
onClick: () => setBlackPerspective((prev) => !prev),
|
||||
title: "switch sides",
|
||||
icon: <Repeat />,
|
||||
},
|
||||
{
|
||||
onClick: () => {
|
||||
chess.undo();
|
||||
rerender();
|
||||
},
|
||||
title: "undo",
|
||||
icon: <CaretLeft />,
|
||||
},
|
||||
{
|
||||
onClick: () => {
|
||||
chess.redo();
|
||||
rerender();
|
||||
},
|
||||
title: "redo",
|
||||
icon: <CaretRight />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ChessUI
|
||||
chess={chess}
|
||||
makeMove={makeMove}
|
||||
blackPerspective={blackPerspective}
|
||||
buttons={buttons}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<ChessUI
|
||||
chess={chess}
|
||||
makeMove={makeMove}
|
||||
blackPerspective={blackPerspective}
|
||||
buttons={buttons}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default LocalGame;
|
||||
|
||||
@@ -4,14 +4,14 @@ type ArgumentTypes<F extends React.FC<any>> = F extends React.FC<infer TT>
|
||||
|
||||
type Head<T extends Array<React.FC<any>>> = T extends [
|
||||
infer TT extends React.FC<any>,
|
||||
...any
|
||||
...any,
|
||||
]
|
||||
? TT
|
||||
: never;
|
||||
|
||||
type Tail<T extends Array<React.FC<any>>> = T extends [
|
||||
any,
|
||||
...infer TT extends Array<React.FC<any>>
|
||||
...infer TT extends Array<React.FC<any>>,
|
||||
]
|
||||
? TT
|
||||
: never;
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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$/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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$/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+20
-19
@@ -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<string>).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<Board>,
|
||||
enPassantSquare: Square
|
||||
enPassantSquare: Square,
|
||||
): Array<InternalMove> {
|
||||
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<Board>,
|
||||
enPassantSquare: Square
|
||||
enPassantSquare: Square,
|
||||
): Array<InternalMove> {
|
||||
if (piece.type == PIECE.PAWN)
|
||||
return generatePawnMoves(position, piece.color, board, enPassantSquare);
|
||||
@@ -524,7 +524,7 @@ export default class Chess {
|
||||
fullMoves: number,
|
||||
kings: Record<Color, number>,
|
||||
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,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user